原文地址: https://zh-hans.reactjs.org/blog/2020/05/22/react-hooks.html
在本文中,我们给函数式组件的函数起个简单一点的名字:render 函数。
在本文中,为了方便描述,对于 render 函数的每次调用,我想称它为一帧。
每一帧拥有独立的变量
function Example(props) {
const { count } = props;
const handleClick = () => {
setTimeout(() => {
alert(count);
}, 3000);
};
return (
<div>
<p>{count}</p>
<button onClick={handleClick}>Alert Count</button>
</div>
);
}
父组件会每秒更新count的值, 当子组件点击Alert Count后, 看到的不是最新的值, 而是3秒前的值, 原因就是: count在每一帧中是独立的, 它一旦在某一帧点击, 它只能看到它那帧的值, 最新帧的值它是获取不到的.
class Example2 extends Component {
handleClick = () => {
setTimeout(() => {
alert(this.props.count);
}, 3000);
};
render() {
return (
<div>
<h2>Example2</h2>
<p>{this.props.count}</p>
<button onClick={this.handleClick}>Alert Count</button>
</div>
);
}
}
然而, 使用class组件取到的是最新帧的值, 因为count在this.props中, 当父组件count刷新后, this.props也会更新, 获取this.props.count还是最新的值
状态
const [state, setState] = useState(initialState);
- 可以通过 useState 等方式拥有局部状态
- 如果是被创建而不是重用, 即在组件的第一帧中, 改状态的值将赋予初始值
initialState, 而重用即第二帧及其后不被赋予初始值 - 状态也是函数作用域下的普通变量。我们可以说每次函数执行拥有独立的状态。
- 在当前帧调用
setState, 在下一帧才能获取值
获取过去或未来帧中的值
const refContainer = useRef(initialValue);
- 在组件的第一帧中,
refContainer.current将被赋予初始值initialValue,之后便不再发生变化。 - 设置它的值不会重新触发
render函数 ref在每一帧都是共享的- 在当前帧设置值, 可以立刻获取值
每一帧可以拥有独立的 Effects
- 对于
useEffect来说,执行的时机是完成所有的 DOM 变更并让浏览器渲染页面后 useEffect的返回值会在当前帧结束时运行
在比对中执行 Effects
- 如果你指定了一个 依赖列表 作为
useEffect、useMemo、useCallback或useImperativeHandle的最后一个参数,它必须包含了回调函数中的所有值. - 因为不管是
props还是state还是变量(以下统称变量), 在不同帧之间都是不可见的, 当effect的依赖列表中未指定回调中使用的变量,这个变量被修改后,effect并不知道, 它仍然停留在之前某一帧中, 那么这个回调中的变量不能被保证是最新值, 导致出现bug.
function Example({ someProp }) {
function doSomething() {
console.log(someProp);
}
useEffect(() => {
doSomething();
}, []); // 🔴 这样不安全(它调用的 `doSomething` 函数使用了 `someProp`)
}
function Example({ someProp }) {
useEffect(() => {
function doSomething() {
console.log(someProp);
}
doSomething();
}, [someProp]); // ✅ 安全(我们的 effect 仅用到了 `someProp`)
}
Effect 的依赖频繁变化
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1); // 这个 effect 依赖于 `count` state
}, 1000);
return () => clearInterval(id);
}, []); // 🔴 Bug: `count` 没有被指定为依赖
return <h1>{count}</h1>;
}
指定 [count] 作为依赖列表就能修复这个 Bug,但会导致每次改变发生时定时器都被重置。所以我们可以使用 setState 的函数式更新形式.
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(c => c + 1); // ✅ 在这不依赖于外部的 `count` 变量
}, 1000);
return () => clearInterval(id);
}, []); // ✅ 我们的 effect 不适用组件作用域中的任何变量
return <h1>{count}</h1>;
}
使用 useMemo/useCallback
- 详细用法参考: useMemo与useCallback使用指南
- useMemo和useCallback都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行;并且这两个hooks都返回缓存的值,useMemo返回缓存的变量,useCallback返回缓存的函数。
- useEffect、useMemo、useCallback都是自带闭包的。也就是说,每一次组件的渲染,其都会捕获当前组件函数上下文中的状态(state, props),所以每一次这三种hooks的执行,反映的也都是当前的状态,你无法使用它们来捕获上一次的状态。对于这种情况,我们应该使用ref来访问。
耗时组件性能优化
function Example(props) {
const [count, setCount] = useState(0);
const [foo] = useState("foo");
const main = (
<div>
<Item key={1} x={1} foo={foo} />
<Item key={2} x={2} foo={foo} />
<Item key={3} x={3} foo={foo} />
<Item key={4} x={4} foo={foo} />
<Item key={5} x={5} foo={foo} />
</div>
);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>setCount</button>
{main}
</div>
);
}
假设 组件,其自身的 render 消耗较多的时间。默认情况下,每次 setCount 改变 count 的值,便会重新对 进行 render,其返回的 React Elements 中3个 也重新 render,其耗时的操作阻塞了 UI 的渲染。导致按下 “setCount” 按钮后出现了明显的卡顿。
为了优化性能,我们可以将 main 变量这一部分单独作为一个组件 ,拆分出去,并对 使用诸如 React.memo , shouldComponentUpdate 的方式,使 count 属性变化时, 不重复 render。
function Example(props) {
const [count, setCount] = useState(0);
const [foo] = useState("foo");
// 使用 useMemo缓存结果
const main = useMemo(() => (
<div>
<Item key={1} x={1} foo={foo} />
<Item key={2} x={2} foo={foo} />
<Item key={3} x={3} foo={foo} />
<Item key={4} x={4} foo={foo} />
<Item key={5} x={5} foo={foo} />
</div>
), [foo]);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>setCount</button>
{main}
</div>
);
}
如何惰性创建昂贵的对象?
useState 可以通过传入函数解决
function Table(props) {
// ⚠️ createRows() 每次渲染都会被调用
const [rows, setRows] = useState(createRows(props.count));
// ...
}
function Table(props) {
// ✅ createRows() 只会被调用一次
const [rows, setRows] = useState(() => createRows(props.count));
// ...
}
useRef 不会 像 useState 那样接受一个特殊的函数重载。相反,你可以编写你自己的函数来创建并将其设为惰性的
function Image(props) {
// ⚠️ IntersectionObserver 在每次渲染都会被创建
const ref = useRef(new IntersectionObserver(onIntersect));
// ...
}
function Image(props) {
const ref = useRef(null);
// ✅ IntersectionObserver 只会被惰性创建一次
function getObserver() {
if (ref.current === null) {
ref.current = new IntersectionObserver(onIntersect);
}
return ref.current;
}
// 当你需要时,调用 getObserver()
// ...
}
受控与非受控
useReducer部分state共享帧
useState在不同帧之间是不可见的, 而useReducer可以保证部分state之间不同帧可见
324

被折叠的 条评论
为什么被折叠?



