如何在React中从道具中更新状态(附代码示例)

66 阅读5分钟

这并不经常发生,但有时你必须从React的道具中更新状态。在这个简短的演练中,我想向你展示一个你想从props中导出状态的用例,以及如何对初始props和更新props进行操作。请记住,这个概念应该只在很少的情况下使用,因为通常不需要从道具中初始化状态。你可以在GitHub上找到我们要建立的这个项目。

如何在React中从prop中导出初始状态

偶尔会有一些用例,你想在React函数组件中从props设置状态。例如,下面的用例显示了这样一个场景:一个父组件提供了一个用户列表作为道具给子组件,子组件将这些用户渲染成一个列表

import React from 'react';

const fakeUsers = [
  {
    id: '1',
    name: 'Robin',
  },
  {
    id: '2',
    name: 'Dennis',
  },
];

function App() {
  const [users, setUsers] = React.useState(fakeUsers);

  return (
    <div>
      <h1>Derive State from Props in React</h1>

      <List list={users} />
    </div>
  );
}

function List({ list }) {
  return (
    <ul>
      {list.map((item) => (
        <Item key={item.id} item={item} />
      ))}
    </ul>
  );
}

function Item({ item }) {
  return (
    <li>
      <span>{item.name}</span>
    </li>
  );
}

export default App;

现在,我们不是在状态中直接处置这些用户,而是模拟一个API请求,通过一个JavaScript承诺的延迟来获得这些用户。在承诺在React的useEffect Hook中解析之前,我们的初始状态中有一个空的用户列表。

import React from 'react';

const fakeUsers = [
  {
    id: '1',
    name: 'Robin',
  },
  {
    id: '2',
    name: 'Dennis',
  },
];

function getFakeUsers() {
  return new Promise((resolve) =>
    setTimeout(() => resolve(fakeUsers), 2000)
  );
}

function App() {
  const [users, setUsers] = React.useState([]);

  React.useEffect(() => {
    const fetchUsers = async () => {
      const data = await getFakeUsers();

      setUsers(data);
    };

    fetchUsers();
  }, []);

  return (
    <div>
      <h1>Derive State from Props in React</h1>

      <List list={users} />
    </div>
  );
}

到目前为止,在不使用任何一种来自道具的派生状态的情况下,一切都应该按预期进行。现在是一个罕见的用例,从props中获取初始状态会很有用。想象一下,我们不是只渲染用户列表,而是为每个用户渲染一个HTML输入域。

function Item({ item }) {
  return (
    <li>
      {item.name}
      <input type="text" value={item.name} />
    </li>
  );
}

由于我们现在有一个输入字段,我们希望能够以某种方式更新其值。例如,想象一下,在渲染这个用户列表时,你希望能够更新他们的名字。目前,我们对这个HTML输入字段的值没有任何访问权限,因为它来自上面的数据状态。我们必须为它分配专门的状态,它只是用来自道具的值来初始化。

function Item({ item }) {
  // derive initial state from props
  const [name, setName] = React.useState(item.name);

  function handleNameChange(event) {
    setName(event.target.value);
  }

  return (
    <li>
      {item.name}
      <input type="text" value={name} onChange={handleNameChange} />
    </li>
  );
}

现在我们从道具中初始化React的状态;每当有人在输入框中输入东西时,这个状态就会被更新,以反映组件中的新值(见React中的控制组件),而不需要关心新的道具,如果组件重新显示的话,因为初始状态只被初始化一次。

如何在React中从props更新状态

第一个例子已经向你展示了如何从props设置初始状态。下一个例子告诉你如何在传入的道具发生变化时从道具中更新状态。与前面的情况类似,不要指望这个用例在你的React应用中出现得太频繁。然而,对于某些用例来说,了解它是很好的。

我们将继续之前的应用。想象一下,每个从我们的列表中渲染出来的用户都会渲染一个按钮,这个按钮有一个内联的事件处理程序来更新用户的名字属性,回调处理程序在我们的App组件的某个地方。

function List({ list, onUpdateName }) {
  return (
    <ul>
      {list.map((item) => (
        <Item key={item.id} item={item} onUpdateName={onUpdateName} />
      ))}
    </ul>
  );
}

function Item({ item, onUpdateName }) {
  // derive initial state from props
  const [name, setName] = React.useState(item.name);

  function handleNameChange(event) {
    setName(event.target.value);
  }

  return (
    <li>
      {item.name}
      <input type="text" value={name} onChange={handleNameChange} />
      <button type="button" onClick={() => onUpdateName(item, name)}>
        Update
      </button>
    </li>
  );
}

为了简单起见,我们可以直接在列表中更新指定的项目--而不必关心这些数据是否真的来自远程数据源--在App组件中使用以下事件处理程序。

function App() {
  const [users, setUsers] = React.useState([]);

  React.useEffect(() => {
    const fetchUsers = async () => {
      const data = await getFakeUsers();

      setUsers(data);
    };

    fetchUsers();
  }, []);

  function handleUpdateName(item, name) {
    const newUsers = users.map((user) => {
      if (user.id === item.id) {
        return {
          id: user.id,
          name: name,
        };
      } else {
        return user;
      }
    });

    setUsers(newUsers);
  }

  return (
    <div>
      <h1>Derive State from Props in React</h1>

      <List list={users} onUpdateName={handleUpdateName} />
    </div>
  );
}

然而,为了使这个例子更加完整和真实,我们也可以调用一个假的API,它将更新的用户列表延迟返回给我们。

function updateFakeUserName(users, id, name) {
  const updatedUsers = users.map((user) => {
    if (user.id === id) {
      return { id, name };
    } else {
      return user;
    }
  });

  return new Promise((resolve) =>
    setTimeout(() => resolve(updatedUsers), 1000)
  );
}

function App() {
  const [users, setUsers] = React.useState([]);

  React.useEffect(() => {
    const fetchUsers = async () => {
      const data = await getFakeUsers();

      setUsers(data);
    };

    fetchUsers();
  }, []);

  async function handleUpdateName(item, name) {
    const newUsers = await updateFakeUserName(users, item.id, name);

    setUsers(newUsers);
  }

  return (
    <div>
      <h1>Derive State from Props in React</h1>

      <List list={users} onUpdateName={handleUpdateName} />
    </div>
  );
}

现在是基于道具更新状态的关键步骤;我们还没有做到这一点。在项目组件中,我们只设置了基于道具的初始状态。我们还没有根据道具来更新状态。然而,输入字段需要在上面的某个地方更新后知道新的状态。下面的小变化说明了这个问题。

function Item({ item, onUpdateName }) {
  // derive initial state from props
  const [name, setName] = React.useState(item.name + '!');

  function handleNameChange(event) {
    setName(event.target.value);
  }

  return (
    <li>
      {item.name}
      <input type="text" value={name} onChange={handleNameChange} />
      <button type="button" onClick={() => onUpdateName(item, name)}>
        Update
      </button>
    </li>
  );
}

在Item组件第一次渲染时,它为输入字段的初始状态在名称上附加了一个!。然而,如果你通过在输入框中写一些全新的东西并点击按钮来更新用户的名字,当输入框重新渲染时,这个用户的新名字并没有被附加上!。我们可以通过更新道具的状态来解决这个问题。

function Item({ item, onUpdateName }) {
  // derive initial state from props
  const [name, setName] = React.useState(item.name + '!');

  function handleNameChange(event) {
    setName(event.target.value);
  }

  // derive updated state from props
  React.useEffect(() => {
    setName(item.name + '!');
  }, [item]);

  return (
    <li>
      {item.name}
      <input type="text" value={name} onChange={handleNameChange} />
      <button type="button" onClick={() => onUpdateName(item, name)}>
        Update
      </button>
    </li>
  );
}

每次传入的道具项发生变化时,我们React的useEffect Hook的副作用就会运行并为我们更新派生状态。我们还可以删除那个小的!帮助器,它的存在只是为了帮我们发现这个错误。

function Item({ item, onUpdateName }) {
  // derive initial state from props
  const [name, setName] = React.useState(item.name);

  function handleNameChange(event) {
    setName(event.target.value);
  }

  // derive updated state from props
  React.useEffect(() => {
    setName(item.name);
  }, [item]);

  return (
    <li>
      {item.name}
      <input type="text" value={name} onChange={handleNameChange} />
      <button type="button" onClick={() => onUpdateName(item, name)}>
        Update
      </button>
    </li>
  );
}

就这样了。从道具中更新状态的概念通常发生在你已经从道具中设置了初始状态的地方。然后当这个状态由于外部来源而被更新时,这里是我们对假的API的请求,我们可能想再次更新这个来自props的初始状态。


现在你知道了在React中从props初始化和更新状态。谨慎地使用这些知识,因为它只适用于某些需要基于props部署状态的情况。然而,大多数情况下,只要在React中使用常识性的props和状态就足够了,你不应该为这个技术担心太多。