react hooks 使用教程
本教程使用简单代码介绍了 react 的 useState,useEffect,useContext,useReducer, useRef,useMemo,useCallback 以及自定义hooks 的用法。
useState
向组件添加一个 状态变量,进行状态管理。
1
| const [state, setState] = useState(initialState)
|
- initialState: 你希望 state 初始化的值。它可以是任何类型的值,但对于函数有特殊的行为。在初始渲染后,此参数将被忽略。
如果传递函数作为 initialState,则它将被视为 初始化函数。它应该是纯函数,不应该接受任何参数,并且应该返回一个任何类型的值。当初始化组件时,React 将调用你的初始化函数,并将其返回值存储为初始状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function App() { const [count, setCount] = useState(0)
const handlePlus = () => { setCount(count + 1) }
const handleMinus = () => { setCount(count => count - 1) }
return ( <> <div>{count}</div> <button onClick={handlePlus}>+</button> <button onClick={handleMinus}>-</button> </>
) }
|
useEffect
useEffect(setup, dependencies?)
dependencies 传入空数组只会在组件更新时触发一次回调函数, setup 函数 在每次依赖项变更重新渲染后, setup 函数可以选择性返回一个
清理(cleanup) 函数,React 将首先使用旧值运行 cleanup 函数
1 2 3
| useEffect(() => { console.log('useEffect') }, []);
|
数组中传入具体的状态,当状态变化时触发回调函数
1 2 3
| useEffect(() => { console.log('useEffect') }, [count]);
|
setup 函数返回的函数称为 cleanup 清理函数
1 2 3 4 5 6 7
| useEffect(() => { console.log('useEffect')
return () => { console.log('clear') } }, [count]);
|
useContext
通过父组件给后代组件传递数据时,如果嵌套的层级太深,可以通过在组件的最顶级调用 useContext 来读取和订阅 context。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const ThemeContext = createContext(null)
function App() { return ( <ThemeContext.Provider value={{color: 'green'}}> <ChildComponent></ChildComponent> </ThemeContext.Provider> ) }
function ChildComponent() { const theme = useContext(ThemeContext) console.log(theme)
return ( <div>Child Component</div> ) }
|
以上代码演示了如何通过在顶层组件设置 context 并且传递数据,内部的组件如何获取数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| const ThemeContext = createContext(null)
function App() { const [theme, setTheme] = useState('green')
const switchTheme = () => { setTheme(theme === 'green' ? 'pink' : 'green') }
return ( <ThemeContext.Provider value={{color: theme}}> <div> <button onClick={ switchTheme }>switch theme</button> </div> <ChildComponent></ChildComponent> </ThemeContext.Provider> ) }
function ChildComponent() { const theme = useContext(ThemeContext) console.log(theme)
return ( <div style={theme}>Child Component</div> ) }
|
通过结合 useState 可以更新 context 并且传递给后代,这里演示了如何 通过 context 全局更新主题颜色。
useReducer
对于复杂的状态设置和管理。
1
| const [state, dispatch] = useReducer(reducer, initialArg, init?)
|
- reducer: 用于更新 state 的纯函数。参数为 state 和 action,返回值是更新后的 state。state 与 action 可以是任意合法值。
- initialArg: 用于初始化 state 的任意值。初始值的计算逻辑取决于接下来的 init 参数。
- 可选参数 init: 用于计算初始值的函数。如果存在,使用 init(initialArg) 的执行结果作为初始值,否则使用 initialArg
通过不同的 reducer type 执行不同的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| const initialState = { count: 0, age: 18, }
const countReducer = (state, action) => { switch (action.type) { case 'increment': return { ...state, count: state.count + 1, } case 'decrement': return { ...state, count: state.count - 1, } default: return state; } }
export function CountProvider() {
const [state, dispatch] = useReducer(countReducer, initialState)
const increment = () => { dispatch({type: 'increment'}) }
const decrement = () => { dispatch({type: 'decrement'}) }
return ( <div> <h1>{state.count}</h1> <button onClick={increment}>+</button> <button onClick={decrement}>-</button> </div> ) }
|
useRef
用于在重新渲染中,去存储易变数据。数据存储在 current 属性上,常用于 DOM 访问。(修改 ref.current 不会触发重新渲染)
1
| const ref = useRef(initialValue)
|
- initialValue: ref 对象的 current 属性的初始值。可以是任意类型的值。这个参数在首次渲染后被忽略。
返回值: useRef 返回一个只有一个属性的对象:
- current:初始值为传递的 initialValue。之后可以将其设置为其他值。如果将 ref 对象作为一个 JSX 节点的 ref 属性传递给 React,React 将为它设置 current 属性。
在后续的渲染中,useRef 将返回同一个对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| export function RefDemo() { const inputRef = useRef(null)
const handleClick = () => { inputRef.current.focus() }
return ( <div> <input ref={inputRef}/> <button onClick={handleClick}> 聚焦输入框 </button> </div> ) }
|
useMemo
在每次重新渲染的时候能够缓存计算的结果
1
| const cachedValue = useMemo(calculateValue, dependencies)
|
- calculateValue:要缓存计算值的函数。它应该是一个没有任何参数的纯函数,并且可以返回任意类型。React 将会在首次渲染时调用该函数;在之后的渲染中,如果 dependencies 没有发生变化,React 将直接返回相同值。否则,将会再次调用 calculateValue 并返回最新结果,然后缓存该结果以便下次重复使用。
- dependencies:所有在 calculateValue 函数中使用的响应式变量组成的数组。响应式变量包括 props、state 和所有你直接在组件中定义的变量和函数。如果你在代码检查工具中 配置了 React,它将会确保每一个响应式数据都被正确地定义为依赖项。依赖项数组的长度必须是固定的并且必须写成 [dep1, dep2, dep3] 这种形式。React 使用 Object.is 将每个依赖项与其之前的值进行比较。
1 2 3 4 5 6 7
| const [count, setCount] = useState(0)
const doubleCount = useMemo(() => { console.log('in use Memo') return count * 2 }, [count])
|
useMemo 在多次重新渲染中缓存了 calculation 函数计算的结果直到依赖项的值发生变化。
另外你可以使用 memo 来优化你的代码,它允许你的组件在 props 没有改变的情况下跳过重新渲染。
1
| const MemoizedComponent = memo(SomeComponent, arePropsEqual?)
|
1 2 3 4 5
| const Greeting = memo(function Greeting({ name }) { return <h1>Hello, {name}!</h1>; });
export default Greeting;
|
useCallback
允许你在多次渲染中缓存函数。
1
| const cachedFn = useCallback(fn, dependencies)
|
fn:想要缓存的函数。此函数可以接受任何参数并且返回任何值。在初次渲染时,React 将把函数返回给你(而不是调用它!)。当进行下一次渲染时,如果 dependencies 相比于上一次渲染时没有改变,那么 React 将会返回相同的函数。否则,React 将返回在最新一次渲染中传入的函数,并且将其缓存以便之后使用。React 不会调用此函数,而是返回此函数。你可以自己决定何时调用以及是否调用。
dependencies:有关是否更新 fn 的所有响应式值的一个列表。响应式值包括 props、state,和所有在你组件内部直接声明的变量和函数。如果你的代码检查工具 配置了 React,那么它将校验每一个正确指定为依赖的响应式值。依赖列表必须具有确切数量的项,并且必须像 [dep1, dep2, dep3] 这样编写。React 使用 Object.is 比较每一个依赖和它的之前的值。(简言之就是依赖项列表中的变量需要被想要缓存的函数内部使用)
返回值:
在初次渲染时,useCallback 返回你已经传入的 fn 函数
在之后的渲染中, 如果依赖没有改变,useCallback 返回上一次渲染中缓存的 fn 函数;否则返回这一次渲染传入的 fn。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| export function UseCallbackDemo() { const [state, setState] = useState('on') const [count, setCount] = useState(0)
const handleAdd = () => { setCount((count) => count + 1) }
const cacheFn = useCallback(() => { console.log(state) }, [state]);
const handleChange = () => { setState(state === 'on' ? 'off' : 'on') }
return ( <div> <button onClick={handleAdd}>add</button> <h3>{count}</h3> <button onClick={handleChange}>change</button> <Child handleClick={cacheFn}></Child> </div> ) }
function Child({handleClick}) { return ( <div> <button onClick={() => handleClick()}>click</button> </div> ) }
|
点击 ‘add’ 会触发父组件的重新渲染,但点击子组件的 Child 的 ‘Click’ 发现打印的内容没有变化。点击 ‘Change’ 才会改变子组件的 handleClick 的打印结果。因为 useCallback 的依赖项里面包含 state 状态。
自定义 hook
自定义一个 useLocalStorage,该 Hook 用于将状态与 localStorage 同步,实现数据的持久化存储
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| export const useLocalStorage = (key, initialValue) => { const [stateValue, setStateValue] = useState(() => { try { const item = localStorage.getItem(key); return item ? JSON.parse(item) : (typeof initialValue === 'function' ? initialValue() : initialValue); } catch (error) { console.log(error) return (typeof initialValue === 'function' ? initialValue() : initialValue); } })
const setStorage = (value) => { let valueToStore; if (typeof value === 'function') { valueToStore = value(stateValue) } else { valueToStore = value } setStateValue(valueToStore) localStorage.setItem(key, JSON.stringify(valueToStore)) }
return [stateValue, setStorage] }
|
1 2 3 4 5 6
| const useToggler = initialState => { const [value, setValue] = React.useState(initialState); const toggleValue = React.useCallback(() => setValue(prev => !prev), []);
return [value, toggleValue]; };
|