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

posted @ 2020-07-24 13:53  Mr.曹  阅读(1071)  评论(0编辑  收藏  举报