React Hooks
Hook 是 React 16.8 的新增特性。它可以让你在编写函数组件的情况下使用 class组件
的生命周期函数以及 state
等 React特性。
1. useState()
基本用法
useState()
是React提供的用来读取和修改 state
的 Hook
。
它传入的第一个参数initialState
为state
的初始值,它的返回一个 state
以及 更新state
的函数setState
。
在初始渲染期间,返回的状态 state
与传入的第一个参数 initialState
值相同。
const [state, setState] = useState(initialState);
setState
函数用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列。
setState(newState);
函数式更新
如果新的 state
需要通过使用先前的 state
计算得出,那么可以将函数传递给 setState
。该函数将接收先前的 state
,并返回一个更新后的值。下面的计数器组件示例展示了 setState
的两种用法:
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>
</>
);
}
“+” 和 “-” 按钮采用函数式形式,因为被更新的 state
需要基于之前的 state
。但是“重置”按钮则采用普通形式,因为它总是把 count
设置回初始值。
注意!
与 Vue 框架中的更新数据不同,useState
不会自动合并更新对象。你可以用函数式的 setState
结合展开运算符来达到合并更新对象的效果。
setState(prevState => {
return {...prevState, ...updatedValues};
});
2. useEffect()
基本概念
useEffect()
给函数组件增加了使用生命周期的能力。它跟 class
组件中的 componentDidMount
、componentDidUpdate
和 componentWillUnmount
具有相同的用途,只不过被合并成了一个 API。
1.模拟 componentDidMount
生命周期函数
useEffect(() => {
// 组件挂载后执行
},[]);
2.模拟 componentDidUpdate
生命周期函数
const [count,setCount] = useState(1);
// 任意 state 变动都会触发
useEffect(() => {
// 任意 state 变动后执行
});
// 指定依赖 state 变动才会触发
useEffect(() => {
// count 变动后执行
},[count]);
3. 模拟 componentWillUnmount
生命周期函数
const [count,setCount] = useState(1);
useEffect(() => {
// 组件挂载后执行
return function(){
// 组件卸载前执行
}
},[]);
3. useContext()
基本用法
const value = useContext(MyContext);
useContext()
是用来解决子组件在接收爷爷辈及往上的 先祖组件传递的 props
嵌套传递的问题的。
它的代码组织方式类似于发布订阅模式:先祖组件提供 <MyContext.Provider>
的value
属性提供context
-> 子组件间通过useContext(MyContext)
拿到context
。
useContext()
接收一个 context
(上下文) 对象(React.createContext
的返回值)并返回该 context
的当前值。当前的 context
值由上层组件中距离当前组件最近的 <MyContext.Provider>
的 value
属性决定。
当组件上层最近的 <MyContext.Provider>
更新时,该 Hook
会触发重渲染,并使用最新传递给 MyContext provider
的 context value
值。
代码示例
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
// 创建 Context
const ThemeContext = React.createContext(themes.light);
// 标明 Context 作用范围 以及 传递的 属性值(value)
function App() {
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
// 拿到 ThemeContext.Provider value 提供的属性值
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}
4. useReducer()
基本概念
const [state, dispatch] = useReducer(reducer, initialArg, init);
useReducer()
是 useState()
的一个替代方案。较之于 useState()
,useReducer()
额外传入一个对state的操作——reducer
(reducer
是一个形如 (state, action) => newState
的函数 ),并返回当前的 state
以及与其配套的 dispatch
方法。
示例程序
以下是用 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>
</>
);
}
5. useRef()
基本用法
const refContainer = useRef(initialValue);
ref
的语义是 “引用”,我们通常用 ref
来访问DOM节点。
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>
</>
);
}
保存数据
在一个组件中有什么东西可以跨渲染周期,也就是在组件被多次渲染之后依旧不变的属性?第一个想到的应该是state
。没错,一个组件的state
可以在多次渲染之后依旧不变。但是,state
的问题在于一旦修改了它就会造成组件的重新渲染。
那么这个时候就可以使用useRef
来跨越渲染周期存储数据,而且对它修改也不会引起组件渲染。
6. useMemo()
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
把“创建”函数和依赖项数组作为参数传入 useMemo()
,它仅会在某个依赖项改变时才重新计算 memoized
值。这种优化有助于避免在每次渲染时都进行高开销的计算。
7. useCallback()
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
把内联回调函数及依赖项数组作为参数传入 useCallback()
,它将返回该回调函数的 memoized
回调函数,该回调函数仅在某个依赖项改变时才会更新。
useCallback(fn, deps)
相当于 useMemo(() => fn, deps)
。