react hooks 使用教程
本教程使用简单代码介绍了 react 的 useState,useEffect,useContext,useReducer, useRef,useMemo,useCallback 以及自定义hooks 的用法。
useState
向组件添加一个 状态变量,进行状态管理。
js
const [state, setState] = useState(initialState)
- initialState: 你希望 state 初始化的值。它可以是任何类型的值,但对于函数有特殊的行为。在初始渲染后,此参数将被忽略。 如果传递函数作为 initialState,则它将被视为 初始化函数。它应该是纯函数,不应该接受任何参数,并且应该返回一个任何类型的值。当初始化组件时,React 将调用你的初始化函数,并将其返回值存储为初始状态。
js
function App() {
const [count, setCount] = useState(0)
const handlePlus = () => {
setCount(count + 1)
}
// setCount 传入函数,第一个参数为 useState 的旧值
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 函数
js
useEffect(() => {
console.log('useEffect')
}, []);
数组中传入具体的状态,当状态变化时触发回调函数
js
useEffect(() => {
console.log('useEffect')
}, [count]);
setup 函数返回的函数称为 cleanup 清理函数
js
useEffect(() => {
console.log('useEffect')
return () => {
console.log('clear')
}
}, [count]);
useContext
通过父组件给后代组件传递数据时,如果嵌套的层级太深,可以通过在组件的最顶级调用 useContext 来读取和订阅 context。
jsx
// 创建 context
const ThemeContext = createContext(null)
function App() {
return (
// 顶层组件传递 context 数据
<ThemeContext.Provider value={{color: 'green'}}>
<ChildComponent></ChildComponent>
</ThemeContext.Provider>
)
}
function ChildComponent() {
// 内部组件获取 context 数据
const theme = useContext(ThemeContext)
console.log(theme) // {color: 'green'}
return (
<div>Child Component</div>
)
}
以上代码演示了如何通过在顶层组件设置 context 并且传递数据,内部的组件如何获取数据。
jsx
// 创建 context
const ThemeContext = createContext(null)
function App() {
const [theme, setTheme] = useState('green')
const switchTheme = () => {
setTheme(theme === 'green' ? 'pink' : 'green')
}
return (
// 传递 context 数据
<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
对于复杂的状态设置和管理。
scss
const [state, dispatch] = useReducer(reducer, initialArg, init?)
- reducer: 用于更新 state 的纯函数。参数为 state 和 action,返回值是更新后的 state。state 与 action 可以是任意合法值。
- initialArg: 用于初始化 state 的任意值。初始值的计算逻辑取决于接下来的 init 参数。
- 可选参数 init: 用于计算初始值的函数。如果存在,使用 init(initialArg) 的执行结果作为初始值,否则使用 initialArg
通过不同的 reducer type 执行不同的操作
jsx
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 不会触发重新渲染)
csharp
const ref = useRef(initialValue)
- initialValue: ref 对象的 current 属性的初始值。可以是任意类型的值。这个参数在首次渲染后被忽略。
返回值: useRef 返回一个只有一个属性的对象:
- current:初始值为传递的 initialValue。之后可以将其设置为其他值。如果将 ref 对象作为一个 JSX 节点的 ref 属性传递给 React,React 将为它设置 current 属性。
在后续的渲染中,useRef 将返回同一个对象。
jsx
// useRef 操作 dom 案例
export function RefDemo() {
const inputRef = useRef(null)
const handleClick = () => {
inputRef.current.focus()
}
return (
<div>
<input ref={inputRef}/>
<button onClick={handleClick}>
聚焦输入框
</button>
</div>
)
}
useMemo
在每次重新渲染的时候能够缓存计算的结果
js
const cachedValue = useMemo(calculateValue, dependencies)
- calculateValue:要缓存计算值的函数。它应该是一个没有任何参数的纯函数,并且可以返回任意类型。React 将会在首次渲染时调用该函数;在之后的渲染中,如果 dependencies 没有发生变化,React 将直接返回相同值。否则,将会再次调用 calculateValue 并返回最新结果,然后缓存该结果以便下次重复使用。
- dependencies:所有在 calculateValue 函数中使用的响应式变量组成的数组。响应式变量包括 props、state 和所有你直接在组件中定义的变量和函数。如果你在代码检查工具中 配置了 React,它将会确保每一个响应式数据都被正确地定义为依赖项。依赖项数组的长度必须是固定的并且必须写成 [dep1, dep2, dep3] 这种形式。React 使用 Object.is 将每个依赖项与其之前的值进行比较。
js
// 省略部分代码。。。
const [count, setCount] = useState(0)
const doubleCount = useMemo(() => {
console.log('in use Memo')
return count * 2
}, [count])
useMemo 在多次重新渲染中缓存了 calculation 函数计算的结果直到依赖项的值发生变化。
另外你可以使用 memo 来优化你的代码,它允许你的组件在 props 没有改变的情况下跳过重新渲染。
ini
const MemoizedComponent = memo(SomeComponent, arePropsEqual?)
jsx
const Greeting = memo(function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
});
export default Greeting;
useCallback
允许你在多次渲染中缓存函数。
ini
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。
jsx
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>
)
}
// eslint-disable-next-line react/prop-types
function Child({handleClick}) {
return (
<div>
<button onClick={() => handleClick()}>click</button>
</div>
)
}
点击 'add' 会触发父组件的重新渲染,但点击子组件的 Child 的 'Click' 发现打印的内容没有变化。点击 'Change' 才会改变子组件的 handleClick 的打印结果。因为 useCallback 的依赖项里面包含 state 状态。
自定义 hook
自定义一个 useLocalStorage,该 Hook 用于将状态与 localStorage 同步,实现数据的持久化存储
js
export const useLocalStorage = (key, initialValue) => {
const [stateValue, setStateValue] = useState(() => {
try {
const item = localStorage.getItem(key);
// 如果存在则解析,否则判断 initialValue 是否为函数
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]
}