状态管理是 React 的基础,虽然useState
可能是最常见的钩子,但可能对其实际行为有些不了解。让我们来看看以下组件:
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [counter, setCounter] = useState(0);
return (
<div className="App">
<h1>Counter: {counter}</h1>
<button
onClick={() => {
setCounter(counter + 1);
setCounter(counter + 1);
}}
>
+
</button>
</div>
);
}
在用户单击按钮后,您希望计数器状态的值是多少?A. 2 B. 1 ✔️
点击demo
原因是在我们的状态更新期间,我们使用了之前的状态值:setCounter(count + 1)。本质上,setState函数被包装在功能组件闭包中,因此它提供了在该闭包中捕获的值
。这意味着当它最终被执行时(setState函数是异步的),它可能持有一个不再相关的状态值。最重要的是,setState 的连续执行可能会导致 React 的调度算法使用相同的事件处理程序处理多个非常快速的状态更新。在异步函数中设置状态时也可能出现同样的问题:
onClick={() => {
setTimout(() => { setCounter(counter + 1); ), 1000);
}};
但是,不用担心,React 实际上为这个问题提供了一个简单的解决方案——“functional updates”。setCounter((prevCounter) => prevCounter + 1);
❝注意:「每当你的状态更新依赖于之前的状态时,请务必使用
functional updates
!」 ❞
// incorrect
const update = useCallback(() => {
setCounter(counter + 1);
}, [counter]);
// correct ✔️
const update = useCallback(() => {
setCounter(prevCounter => prevCounter + 1);
}, []);
我们习惯于使用 React 中的 ref 机制作为访问元素的 DOM 节点的手段,无论是因为我们需要它来计算其大小、设置焦点状态,或者基本上做任何 React 自然不能做的事情。但是 refs 也可以用于不同的目的——我们可以使用类组件非常容易·实现这一点,但我们不能使用函数式组件——保留一个不会在每次渲染时重新创建的静态变量
。
点击demo
❝在函数式组件中我们可以使用ref存储静态变量 ❞
写入DOM的成本非常高。这就是为什么我们通常不想重新mount 组件,除非绝对必要。但是有时我们必须,出于各种原因。那么在那种情况下,我们如何告诉 react 卸载并立即重新mount 组件?用一个简单的技巧——为我们的组件提供一个key,并改变它的值。
❝
key prop 是一个特殊的 React 属性
❞
著名的 React 警告
image
key是帮助 React 跟踪元素的东西,即使我们已经改变了它在组件结构中的位置或重新渲染了父级(否则每次渲染都会导致整个组件数组被重新安装,这是不好的性能
)。
使用这种机制,我们可以欺骗 React 认为一个组件与其之前的自己不同,并导致它重新挂载。
点击demo
import React, { useEffect, useState, useCallback } from "react";
import "./styles.css";
export default function App() {
const [key, setKey] = useState(1);
const [console, setConsole] = useState([]);
const onLifecycleChange = useCallback((message) => {
setConsole((prevConsole) => [message, ...prevConsole]);
}, []);
return (
<div className="App">
<button
onClick={() => {
setKey((oldKey) => oldKey + 1);
}}
>
Remount
</button>
<ChildComp key={key} onLifecycleChange={onLifecycleChange} />
<div className="console">
{console.map((text, i) => (
<div key={i}>{text}</div>
))}
</div>
</div>
);
}
const ChildComp = React.memo(({ onLifecycleChange }) => {
useEffect(() => {
onLifecycleChange("mounting ChildComp");
return () => {
onLifecycleChange("ummounting ChildComp");
};
}, [onLifecycleChange]);
return <div style={{ marginTop: 10 }}>Child Comp</div>;
});
❝Context用来解决 “prop drilling” 问题,但是它会带来性能问题,(context value如果是对象)其中一个属性状态发生变化,会导致其它订阅Context的组件都发生更新,所以context一般用于不频繁更新的场景比如(locale和theme) ❞
import React, { useState, useContext } from "react";
import "./styles.css";
const SomeContext = React.createContext({});
export default function App() {
const [contextValue, setContextValue] = useState({ name: "John", age: 55 });
const onChangeAge = (e) => {
const age = e.target.value;
setContextValue((prevContextValue) => ({ ...prevContextValue, age }));
};
const onChangeName = (e) => {
const name = e.target.value;
setContextValue((prevContextValue) => ({ ...prevContextValue, name }));
};
return (
<div className="App">
<SomeContext.Provider value={contextValue}>
<Wrapper />
<input value={contextValue.age} onChange={onChangeAge} />
<input value={contextValue.name} onChange={onChangeName} />
</SomeContext.Provider>
</div>
);
}
const Wrapper = () => {
return (
<div>
<Name />
<Age />
</div>
);
};
const Name = () => {
const { name } = useContext(SomeContext);
console.log("name rendered");
return <h1>Name: {name}</h1>;
};
const Age = () => {
const { age } = useContext(SomeContext);
console.log("age rendered");
return <h1>Age: {age}</h1>;
};
点击demo
❝React为Children属性提供了一系列API ❞
React.Children.toArray(children)
// If you want to use map/forEach:
React.Children.map(children, fn)
React.Children.forEach(children, fn)
React.Children.count(children)
❝如果你需要在您的组件中强制执行单个子项(我最近注意到 formik 这样做),你可以简单地在您的组件中包含以下行,React 将为你运行检查和错误处理: ❞
React.Children.only(children)
import React from "react";
import "./styles.css";
export default function App() {
return (
<div className="App">
<Wrapper>
<h2 style={{ color: "red", margin: 0 }}>Red</h2>
<h2 style={{ color: "blue" }}>Blue</h2>
<h2 style={{ color: "green" }}>Green</h2>
</Wrapper>
<Wrapper>hello</Wrapper>
</div>
);
}
const Wrapper = ({ children }) => {
const childrenArray = React.Children.toArray(children);
console.log(childrenArray);
return (
<div style={{ border: "1px solid", padding: 20, margin: 5 }}>
<div>{children}</div>
<div>Number of children: {React.Children.count(children)}</div>
<div>
children type: <strong>{typeof children}</strong>
</div>
</div>
);
};
点击demo