React Hooks 用法总结

react

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

以上是 React Hooks 用法总结 的全部内容, 来源链接: utcz.com/z/383969.html

回到顶部