【问题标题】:React Hooks: using useState inside useEffect doesn't set the state immediately after first renderingReact Hooks:在 useEffect 中使用 useState 不会在第一次渲染后立即设置状态
【发布时间】:2020-01-06 18:21:46
【问题描述】:

这是我的代码:https://codesandbox.io/s/gatsby-starter-default-og782

逻辑很简单:我用 Gatsby.js 和 React 渲染了一个Gallery 组件

<StaticQuery
    query={graphql`
      {
        allFile(
          filter: {
            extension: { regex: "/(jpg)|(jpeg)|(png)/" }
            sourceInstanceName: { eq: "images" }
          }
        ) {
          edges {
            node {
              childImageSharp {
                fluid(maxWidth: 800, quality: 95) {
                  aspectRatio
                  src
                  srcSet
                  originalName
                  srcWebp
                  srcSetWebp
                  sizes
                }
              }
            }
          }
        }
      }
    `}
    render={data => (
      <Layout>
        <Gallery minWidth={200} data={data.allFile.edges} />
      </Layout>
    )}
  />

这将为Gallery 提供要显示的图像文件。

Gallery里面,我有这些状态

  const [allPics, setAllPics] = useState([]) <-- storing all the image files
  const [currentImgs, setCurrentImgs] = useState([]) <-- storing the images of the current tab
  const [tabs, setTabs] = useState([])

useEffect 在组件挂载时只运行一次

useEffect(() => {
    const allPics = data.map((img, i) => {
      img.node.childImageSharp.index = i
      return img.node.childImageSharp
    })
    setAllPics(allPics)
    setTabs(getAllTabs(allPics))
    // default tab when the page first loads
    setCurrentImgs(
      allPics.filter(({ fluid }) => fluid.originalName.includes(tabs[0]))
    )
  }, [data])

然后我用这个渲染

<>
      <ProjectsContainer>
        {tabs.map(tab => (
          <Project
            key={tab}
            onClick={() => {
              setCurrentImgs(
                allPics.filter(({ fluid }) => fluid.originalName.includes(tab))
              )
            }}
          >
            {tab}
          </Project>
        ))}
      </ProjectsContainer>

      <Mansory gap={"0em"} minWidth={minWidth}>
        {currentImgs.map((img, i) => {
          return (
            <PicContainer
              index={img.index}
              selected={isSelected}
              key={img.index}
            >
              <Enlarger
                src={img.fluid.srcSet.split(" ")[0]}
                enlargedSrc={img.fluid.src}
                index={img.index}
                setIsSelected={setIsSelected}
                onLoad={() => {
                  setTimeout(() => {
                    refs[img.index].current.toggleOpacity(1)
                  }, 300)
                }}
                ref={refs[img.index]}
                realIndex={i}
              />
            </PicContainer>
          )
        })}
      </Mansory>
    </>

问题是,状态currentImgs 似乎仍然是一个空数组,这是useEffect 运行后的默认状态,所以当我们第一次加载页面时,没有当前图像可以显示。您必须手动单击这些选项卡中的任何一个才能再次触发setCurrentImgs 以显示当前图像。

所以问题似乎很明显,就是为什么useEffect中只有currentImgs状态设置不正确,而allPicstabs等其他状态设置正确?

【问题讨论】:

    标签: javascript reactjs frontend gatsby


    【解决方案1】:

    就像基于类的 React 组件的生命周期方法,特别是 setState 的异步性质,功能对应物 useState 钩子也是如此。在当前渲染周期中,钩子的当前值保持静态,直到“刷新”更新并重新渲染组件。在您的useEffect 钩子中,您试图访问尚未“同步”的tabs 状态值。如果你想要这个渲染周期中的值,你应该直接访问 tabs 响应数组。

    useEffect(() => {
      const allPics = data.map((img, i) => {
        img.node.childImageSharp.index = i
        return img.node.childImageSharp
      })
      setAllPics(allPics)
    
      // get all tabs
      const tabsArray = getAllTabs(allPics);
    
      setTabs(tabsArray)
    
      // default tab when the page first loads
      setCurrentImgs(
        allPics.filter(({ fluid }) => fluid.originalName.includes(tabsArray[0]))
      )
    }, []) // pass empty dependency array if you truly only want this to run on mount
    

    【讨论】:

      【解决方案2】:

      您不需要使用useEffect 进行此初始状态加载。您可以在 useState 挂钩中设置状态的初始值,如下所示:

      const [allPics, setAllPics] = useState(
        data.map((img, i) => {
          img.node.childImageSharp.index = i
          return img.node.childImageSharp
        })
      )
      const getAllTabs = imageObj => {
        return imageObj
          .map(({ fluid }) => fluid.originalName.split("-")[0])
          .filter((name, index, self) => index === self.indexOf(name))
      }
      const [tabs, setTabs] = useState(getAllTabs(allPics))
      const [currentImgs, setCurrentImgs] = useState(
        allPics.filter(({ fluid }) => fluid.originalName.includes(tabs[0]))
      )
      

      您可以通过这些更改查看沙盒的工作分支here

      【讨论】:

      • 您好,谢谢您的回答。实际上我想到了这个解决方案,但是通过将这些 set-initial-value 函数移到useEffect 之外,我们不是每次组件重新渲染时都会调用它们吗?就像我将console.log("here") 放在那些useState 中,你放在useEffect 之外,然后当我在选项卡之间导航时,我们可以看到useState 在初始设置中被多次调用,因为知道“这里”被打印。我认为这可能会影响性能。
      • useState 初始化器仅在组件挂载时调用一次。如果在切换选项卡时它被卸载,那么它会在安装时再次被调用。
      【解决方案3】:

      React useEffect 使用会话直到每次更新/或者我们可以说生命周期直到更改。 只需从 useState 钩子传递 useEffect 中的参数,它就会更新数据,

      不要尝试传递数据变量,它将运行无限循环。只需传递 setData 函数即可。

      React.useEffect(()=>{
              console.log("effect");
      
              (async () => {
                  try {
                      let result = await fetch("/query/countries");
                      const res = await result.json();
      
                      let result1 = await fetch("/query/projects");
                      const res1 = await result1.json();
      
      
                      let result11 = await fetch("/query/regions");
                      const res11 = await result11.json();
                      setData({countries: res, projects: res1, regions: res11});
      
                  } catch {
      
                  }
              })()
          }, [setData])
      
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-05-12
        • 2021-09-09
        • 2019-06-26
        • 2019-11-01
        • 1970-01-01
        • 2019-10-11
        • 1970-01-01
        • 2019-07-31
        相关资源
        最近更新 更多