React Hooks 用法总结
1. useState: 状态钩子
基础用法
const [state, setState] = useState(initialState);
返回一个 state,以及更新 state 的函数。
在初始渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同。
setState 函数用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列。
函数式更新
如果新的 state 需要通过使用先前的 state 计算得出,那么可以将函数传递给 setState。该函数将接收先前的 state,并返回一个更新后的值。下面的计数器组件示例展示了 setState 的两种用法:
import React, { useState } from "react";
function Counter({ initialCount }) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount((prevCount) => prevCount - 1)}>-</button>
<button onClick={() => setCount((prevCount) => prevCount + 1)}>+</button>
</>
);
}
function Parent() {
return <Counter initialCount={1} />;
}
export default Parent;
注意
与 class 组件中的 setState 方法不同,useState 不会自动合并更新对象。你可以用函数式的 setState 结合展开运算符来达到合并更新对象的效果。
setState(prevState => {
return {...prevState, ...updatedValues};
});
惰性初始 state
initialState 参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用:
const getInitState = () => {
const initialState = someExpensiveComputation(props);
return initialState;
}
const [state, setState] = useState(getInitState);
2. useEffect():副作用钩子
用法
useEffect()用来引入具有副作用的操作,最常见的就是向服务器请求数据。以前,放在componentDidMount、componentDidUpdate里面的代码,现在可以放在useEffect()。
useEffect(() => {
// Async Action
}, [dependencies])
- 上面用法中,useEffect()接受两个参数。第一个参数是一个函数,异步操作的代码放在里面。第二个参数是一个数组,用于给出 Effect 的依赖项,只要这个数组发生变化,useEffect()就会执行。
- 第二个参数可以省略,这时每次组件渲染时,就会执行useEffect()。
- 如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。
举例
const Person = ({ personId }) => {
const [loading, setLoading] = useState(true);
const [person, setPerson] = useState({});
useEffect(() => {
setLoading(true);
fetch(`/api/url`)
.then(data => {
setPerson(data);
setLoading(false);
});
}, [personId])
if (loading === true) {
return <p>Loading ...</p>
}
return (
<div>
<p>{person.name}</p>
</div>
)
}
上面代码中,每当组件参数personId发生变化,useEffect()就会执行。组件第一次渲染时,useEffect()也会执行。
3. useContext():共享状态钩子
如果需要在组件之间共享状态,可以使用useContext()。
现在有两个组件 Navbar 和 Messages,我们希望它们之间共享状态。
<div className="App">
<Navbar/>
<Messages/>
</div>
第一步就是使用 React Context API,在组件外部建立一个 Context。
const AppContext = React.createContext({});
组件封装代码如下。
<AppContext.Provider value={{
username: 'superawesome'
}}>
<div className="App">
<Navbar/>
<Messages/>
</div>
</AppContext.Provider>
上面代码中,AppContext.Provider提供了一个 Context 对象,这个对象可以被子组件共享。
Navbar 组件的代码如下。
const Navbar = () => {
const { username } = useContext(AppContext);
return (
<div className="navbar">
<p>AwesomeSite</p>
<p>{username}</p>
</div>
);
}
上面代码中,useContext()钩子函数用来引入 Context 对象,从中获取username属性。
Message 组件的代码也类似。
const Messages = () => {
const { username } = useContext(AppContext)
return (
<div className="messages">
<h1>Messages</h1>
<p>1 message for {username}</p>
<p className="message">useContext is awesome!</p>
</div>
)
}
4. useReducer():action 钩子
用法
useReducer 可以作为 useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。
const [state, dispatch] = useReducer(reducer, initialState);
以下是用 reducer 重写 useState 一节的计数器示例:
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
惰性初始化
可以选择惰性地创建初始 state。为此,需要将 init 函数作为 useReducer 的第三个参数传入,这样初始 state 将被设置为 init(initialArg)。
这么做可以将用于计算 state 的逻辑提取到 reducer 外部,这也为将来对重置 state 的 action 做处理提供了便利:
function init(initialCount) {
return {count: initialCount};
}
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
case 'reset':
return init(action.payload);
default:
throw new Error();
}
}
function Counter({initialCount}) {
const [state, dispatch] = useReducer(reducer, initialCount, init);
return (
<>
Count: {state.count}
<button
onClick={() => dispatch({type: 'reset', payload: initialCount})}>
Reset
</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
5. useMemo()
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
例如:
const Child = memo(({ data }) => {
console.log("子组件 render...", data.name);
return (
<div>
<div>child</div>
<div>{data.name}</div>
</div>
);
});
const Parent = () => {
console.log("父组件 render...");
const [count, setCount] = useState(0);
const [name, setName] = useState("haha");
const data = {
name,
};
return (
<div>
<div>{count}</div>
<button onClick={() => setCount(count + 1)}> 更新计数+1 </button>
<Child data={data} />
</div>
);
};
export default Parent;
当点击按钮更新count的时候,Parent组件会render,一旦render, 执行到这一行代码:
const data = {
name,
};
这一行代码会生成有新的内存地址的对象,那么就算带着memo的Child组件,也会跟着重新render, 尽管最后其实Child使用到的值没有改变。
这样Child就多余render了,造成性能浪费,于是useMemo 作为一个有着暂存能力的钩子,就派上用场了。
const Child = memo(({ data }) => {
console.log("子组件 render...", data.name);
return (
<div>
<div>child</div>
<div>{data.name}</div>
</div>
);
});
const Parent = () => {
console.log("父组件 render...");
const [count, setCount] = useState(0);
const [name, setName] = useState("haha");
const data = useMemo(() => {
return {
name,
};
}, [name]);
return (
<div>
<div>{count}</div>
<button onClick={() => setCount(count + 1)}> 更新计数+1 </button>
<Child data={data} />
</div>
);
};
当执行到代码:
const data = useMemo(() => {
return {
name,
};
}, [name]);
会先根据[name]里面的name值判断一下,因为useMemo 作为一个有着暂存能力的,暂存了上一次的name结果。如果name值没有变化,那么data就不会重新赋值,没有新的对象,就没有新的内存地址,所以 Child 就不会重新render了。
6. useCallback
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
useCallback 和 useMemo 都可缓存函数的引用或值,但是从更细的使用角度来说 useCallback 缓存函数的引用,useMemo 缓存计算数据的值。
useCallback(fn, deps)
相当于 useMemo(() => fn, deps)
。
7. useRef
const refContainer = useRef(initialValue);
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
参考:http://www.ruanyifeng.com/blog/2019/09/react-hooks.html; https://juejin.im/post/5e53d9116fb9a07c9070da44#heading-9