Javascript 使用React useState()钩子更新和合并状态对象

Javascript 使用React useState()钩子更新和合并状态对象,javascript,reactjs,react-hooks,Javascript,Reactjs,React Hooks,我发现这两个文件有点混乱。使用状态挂钩更新状态对象的最佳实践是哪一种 假设一个用户希望进行以下状态更新: INITIAL_STATE = { propA: true, propB: true } stateAfter = { propA: true, propB: false // Changing this property } 选项1 从这篇文章中,我们发现这是可能的: const [count, setCount] = useState(0); setCount(c

我发现这两个文件有点混乱。使用状态挂钩更新状态对象的最佳实践是哪一种

假设一个用户希望进行以下状态更新:

INITIAL_STATE = {
  propA: true,
  propB: true
}

stateAfter = {
  propA: true,
  propB: false   // Changing this property
}
选项1

从这篇文章中,我们发现这是可能的:

const [count, setCount] = useState(0);
setCount(count + 1);
所以我可以做:

const [myState, setMyState] = useState(INITIAL_STATE);
然后:

setMyState({
  ...myState,
  propB: false
});
选项2

从中我们得到:

与在类组件中找到的setState方法不同,useState是这样做的 不自动合并更新对象。你可以复制这个 将函数更新程序窗体与对象扩展相结合的行为 语法:


据我所知,两者都有效。那么,有什么区别呢?哪一个是最佳实践?我应该使用pass函数(选项2)访问以前的状态,还是应该使用扩展语法(选项1)访问当前状态?

这两个选项都是有效的,但就像类组件中的
setState
一样,在更新从已处于状态的内容派生的状态时需要小心

例如,如果您在一行中更新一个计数两次,如果您不使用更新状态的函数版本,它将无法按预期工作

const{useState}=React;
函数App(){
const[count,setCount]=useState(0);
函数断开增量(){
设置计数(计数+1);
设置计数(计数+1);
}
函数增量(){
setCount(count=>count+1);
setCount(count=>count+1);
}
返回(
{count}
断开增量
增量
);
}
render(,document.getElementById(“根”))

最佳做法是使用单独的呼叫:

const [a, setA] = useState(true);
const [b, setB] = useState(true);
选项1可能会导致更多的bug,因为这样的代码通常会在一个具有过时值
myState
的闭包中结束

当新状态基于旧状态时,应使用选项2:

setCount(count => count + 1);
对于复杂的状态结构,考虑使用

对于共享某些形状和逻辑的复杂结构,可以创建自定义挂钩:

function useField(defaultValue) {
  const [value, setValue] = useState(defaultValue);
  const [dirty, setDirty] = useState(false);
  const [touched, setTouched] = useState(false);

  function handleChange(e) {
    setValue(e.target.value);
    setTouched(true);
  }

  return {
    value, setValue,
    dirty, setDirty,
    touched, setTouched,
    handleChange
  }
}

function MyComponent() {
  const username = useField('some username');
  const email = useField('some@mail.com');

  return <input name="username" value={username.value} onChange={username.handleChange}/>;
}
函数使用字段(默认值){
const[value,setValue]=使用状态(defaultValue);
const[dirty,setDirty]=useState(false);
const[toucted,settoucted]=useState(false);
功能手柄更改(e){
设定值(即目标值);
settouch(真);
}
返回{
值,设置值,
肮脏,肮脏,
感动,感动,,
手换
}
}
函数MyComponent(){
const username=useField('some username');
const email=useField('some@mail.com');
返回;
}

对于该用例来说,两者都非常好。传递给
setState
的函数参数只有在您希望通过区分前一个状态来有条件地设置状态时才真正有用(我的意思是您可以通过调用
setState
的逻辑来实现,但我认为它在函数中看起来更干净)或者,如果您在一个闭包中设置了状态,而该闭包不能立即访问以前状态的最新版本

例如,事件侦听器在装载到窗口时仅绑定一次(无论出于何种原因)。例如

useEffect(function() {
  window.addEventListener("click", handleClick)
}, [])

function handleClick() {
  setState(prevState => ({...prevState, new: true }))
}

如果
handleClick
仅使用选项1设置状态,它看起来像
setState({…prevState,new:true})
。但是,这可能会引入错误,因为
prevState
只会在初始渲染时捕获状态,而不会从任何更新中捕获状态。传递给
setState
的函数参数始终可以访问状态的最新迭代。

一个或多个关于状态类型的选项可能适合您的用例

通常,您可以按照以下规则来决定所需的状态类型

第一:各个州是否相关

如果应用程序中的各个状态彼此相关,则可以选择将它们组合在一个对象中。否则,最好将它们分开,并使用多个
useState
,以便在处理特定的处理程序时,您只更新relavantstate属性,而不关心其他属性

例如,诸如
名称、电子邮件等用户属性是相关的,您可以将它们组合在一起,而对于维护多个计数器,您可以使用
多个useState挂钩

Second:更新状态的逻辑是否复杂,取决于处理程序或用户交互

在上述情况下,最好使用
useReducer
进行状态定义。当您尝试创建例如和todo应用程序时,这种情况非常常见,您希望
更新
创建
删除不同交互上的
元素

我应该使用pass函数(选项2)访问上一个 状态,还是应该使用扩展语法访问当前状态 (备选方案1)

使用钩子的状态更新也是批处理的,因此,每当您想要基于前一个更新状态时,最好使用回调模式

当setter由于只定义了一次而没有从封闭闭包接收更新值时,updatestate的回调模式也很有用。例如,当添加更新事件状态的侦听器时,仅在初始渲染时调用
useffect

使用状态挂钩更新状态对象的最佳实践是哪一种

正如其他答案所指出的那样,它们都是有效的

有什么区别

这种混淆似乎是由于
“与类组件中的setState方法不同,useState不会自动合并更新对象”
,尤其是“合并”部分

让我们比较一下
this.setState
useState

class SetStateApp extends React.Component {
  state = {
    propA: true,
    propB: true
  };

  toggle = e => {
    const { name } = e.target;
    this.setState(
      prevState => ({
        [name]: !prevState[name]
      }),
      () => console.log(`this.state`, this.state)
    );
  };
  ...
}

function HooksApp() {
  const INITIAL_STATE = { propA: true, propB: true };
  const [myState, setMyState] = React.useState(INITIAL_STATE);

  const { propA, propB } = myState;

  function toggle(e) {
    const { name } = e.target;
    setMyState({ [name]: !myState[name] });
  }
...
}

它们都在
切换中切换
propA/B
<
class SetStateApp extends React.Component {
  state = {
    propA: true,
    propB: true
  };

  toggle = e => {
    const { name } = e.target;
    this.setState(
      prevState => ({
        [name]: !prevState[name]
      }),
      () => console.log(`this.state`, this.state)
    );
  };
  ...
}

function HooksApp() {
  const INITIAL_STATE = { propA: true, propB: true };
  const [myState, setMyState] = React.useState(INITIAL_STATE);

  const { propA, propB } = myState;

  function toggle(e) {
    const { name } = e.target;
    setMyState({ [name]: !myState[name] });
  }
...
}

    useState<S>(
      initialState: (() => S) | S,
    ): [S, Dispatch<BasicStateAction<S>>] {
      currentHookNameInDev = 'useState';
        ...
      try {
        return updateState(initialState);
      } finally {
        ...
      }
    },
function updateState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  return updateReducer(basicStateReducer, (initialState: any));
}

    useReducer<S, I, A>(
      reducer: (S, A) => S,
      initialArg: I,
      init?: I => S,
    ): [S, Dispatch<A>] {
      currentHookNameInDev = 'useReducer';
      updateHookTypesDev();
      const prevDispatcher = ReactCurrentDispatcher.current;
      ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
      try {
        return updateReducer(reducer, initialArg, init);
      } finally {
        ReactCurrentDispatcher.current = prevDispatcher;
      }
    },
- Through Input

        const [state, setState] = useState({ fName: "", lName: "" });
        const handleChange = e => {
        const { name, value } = e.target;
        setState(prevState => ({
            ...prevState,
            [name]: value
        }));
        };

        <input
            value={state.fName}
            type="text"
            onChange={handleChange}
            name="fName"
        />
        <input
            value={state.lName}
            type="text"
            onChange={handleChange}
            name="lName"
        />
   ***************************

 - Through onSubmit or button click

        setState(prevState => ({
            ...prevState,
            fName: 'your updated value here'
         }));