代码之家  ›  专栏  ›  技术社区  ›  OliverRadini

如何避免与React挂钩相关的范围问题

  •  0
  • OliverRadini  · 技术社区  · 5 年前

    问题所在

    每当我尝试使用钩子做任何远程复杂和异步的事情时,我都会遇到一个反复出现的问题。

    导致问题的时间线大致如下:

    1. 在钩子中定义一些状态 s
    2. 进行异步调用以获取一些数据 d 并使用它来设置状态 s
    3. 根据以下内容拨打后续电话 d ,并使用它进行更新 s

    问题是在3。;当我想更新状态时 s ,在这一点上是 s 因为它是在定义函数的范围内定义的,即它没有最新更新状态的概念,只有它在定义函数时知道的状态。

    我的半解决方案

    我已经能够使用一些东西来解决这个问题,其中一些东西往往有效:

    1. 对状态进行去规范化(无论如何可能是个好主意),这样我们就可以尽可能地避免更新状态中的现有项目
    2. 超时后运行更新;使用 setTimeout 强制在下一个渲染周期中进行更新
    3. 完全忘记使用状态挂钩,从外部管理状态,并将所有内容作为道具传入

    这些解决方案并不总是(或从来都不是)好的或可取的。我猜我在这里错过了一些基本的东西,但我在网上找不到任何关于这方面的信息,而且我还没有从同事那里得到任何可行的建议。

    太长,读不下去了

    在React函数组件的主体中定义的函数将使用它们当时可以访问的范围来定义。这并不总是最新的状态。我怎样才能绕过这个问题?


    一个任意的例子来证明我在说什么:

    const getUsers = () => Promise.resolve({
      1: {
        name: 'Mr 1',
        favouriteColour: null,
      },
      2: {
        name: 'Ms 2',
        favouriteColour: null,
      },
    });
    
    const getFavouriteColorForUser = (id) => Promise.resolve({ id, color: Math.random() > 0.5 ? 'red' : 'blue' });
    
    
    const App = () => {
      const [users, setUsers] = React.useState({});
      
      const handleClick = () => {
        getUsers()
          .then(data => {
            setUsers(data);
            return Promise.resolve(data);
          })
          .then(data => {
            return Promise.all(Object.keys(data).map(getFavouriteColorForUser));
          })
          .then(data => {
            const updatedUsers = {
              ...users,
              ...data.reduce(
                (p, c) => ({
                  ...p,
                  [c.id]: {
                    ...users[c.id],
                    favouriteColor: c.color
                  },
                }),
                {}
              )
            };
            
            console.log('users:');
            console.dir(users);
            console.log('color data:');
            console.dir(data);
            console.log('updatedUsers:');
            console.dir(updatedUsers);
            
            setUsers(updatedUsers);
          })
      }
      
      return (
        <div>
          {Object.keys(users).map(k => users[k]).map(user => (
            <div>{user.name}'s favourite colour is {user.favouriteColour}</div>
          ))}
          <button onClick={handleClick}>Get Users</button>
        </div>
      );
    }
    
    ReactDOM.render(<App />, document.getElementById('app'))
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
    <div id="app"></div>
    1 回复  |  直到 5 年前
        1
  •  4
  •   Nicholas Tower    5 年前

    setState有一个变体,你可以在其中传递一个函数。保证使用状态中的最新值调用该函数。你可以用它来计算你的下一个状态:

    .then(data => {
      setUsers(previousUsers => {
        const updatedUsers = {
          ...previousUsers, // <--- using previousUsers, not users
          ...data.reduce(
            (p, c) => ({
              ...p,
              [c.id]: {
                ...previousUsers[c.id], // <--- using previousUsers, not users
                favouriteColor: c.color
              },
            }), {})
        };
    
        return updatedUsers;
      })
    })