在线案例演示

useState

  • useState 返回一对值,包括值和改变该值的方法
  • 参数,初始化的state
  • 定义: const [state, setState] = useState(initialState)

    useState使用案例

    1. import React, { useState } from "react";
    2. import ReactDOM from "react-dom";
    3. function App() {
    4. // 定义num值为0,并设置一个改变num值的方法setNum
    5. const [num, setNum] = useState(0);
    6. return (
    7. <div>
    8. <p>{num}</p>
    9. <button onClick={() => setNum(num + 1)}>+</button>
    10. </div>
    11. );
    12. }
    13. ReactDOM.render(<App />, document.getElementById("root"));

useState源码实现

当只有调用useState一次时

  1. let lastState;
  2. function useState(initialState) {
  3. lastState = lastState || initialState;
  4. function setState(newState) {
  5. lastState = newState;
  6. render();
  7. }
  8. return [lastState, setState];
  9. }

多次调用useState

  1. // 实现useState, 修复使用多次useState出现的问题
  2. // 通过定义个数组对象,以及stateIndex索引,每次调用useState都把索引增加,可以进行区别不同的useState
  3. let lastStates = [];
  4. let stateIndex = 0;
  5. function useState(initialState) {
  6. lastStates[stateIndex] = lastStates[stateIndex] || initialState;
  7. let currentIndex = stateIndex;
  8. function setState(newState) {
  9. lastStates[currentIndex] = newState;
  10. render();
  11. stateIndex = 0;
  12. }
  13. return [lastStates[stateIndex++], setState];
  14. }

useMemo 和 useCallback

单纯使用useState存在性能问题,如果更新父组件,即使没有改动子元素的数据,子元素也会进行更新,可以使用useMemo对数据进行缓存

  • useMemo用于缓存对象数据
  • useCallback用于缓存函数

存在的问题

  1. function App() {
  2. // 定义num值为0,并设置一个改变num值的方法setNum
  3. const [num, setNum] = useState(0);
  4. const [name, setName] = useState("state");
  5. let addClick = () => setNum(num + 1);
  6. return (
  7. <div>
  8. <input value={name} onChange={(event) => setName(event.target.value)} />
  9. <Child number={num} addClick={addClick} />
  10. </div>
  11. );
  12. }
  13. function Child({ number, addClick }) {
  14. // 通过input每次更新父组件name的值时,也会执行Child组件的render,如果子组件非常庞大造成性能的影响
  15. console.log("render child");
  16. return <button onClick={addClick}>{number}</button>;
  17. }

使用

为了解决父组件数据的变化,触发子组件更新的问题,可以使用useMemo缓存数据
为了解决父组件数据的变化,触发子组件更新的问题,可以使用useCallback缓存函数

  1. function App() {
  2. // 定义num值为0,并设置一个改变num值的方法setNum
  3. const [num, setNum] = useState(0);
  4. const [name, setName] = useState("state");
  5. // useMemo用于缓存对象数据,
  6. // 第二个参数是依赖项,控制number依赖哪个数据进行变化,
  7. // 如果为[]就会根据第一个参数中的返回值进行变化;如果不填写就会一直变,触发父组件的name,子组件也会render
  8. let number = useMemo(() => num, [num]);
  9. // useCallback用于缓存函数
  10. // 第二个参数是依赖项,控制addClick依赖哪个数据进行变化,
  11. // 如果为[]就会根据第一个参数中的函数的返回值进行变化;如果不填写就会一直变,触发父组件的name,子组件也会render
  12. let addClick = useCallback(() => setNum(num + 1), [num]);
  13. return (
  14. <div>
  15. <input value={name} onChange={(event) => setName(event.target.value)} />
  16. <Child number={number} addClick={addClick} />
  17. </div>
  18. );
  19. }
  20. function Child({ number, addClick }) {
  21. // 当每次更新父组件name的值时,也会执行Child组件的render,如果子组件非常庞大造成性能的影响
  22. console.log("render child");
  23. return <button onClick={addClick}>{number}</button>;
  24. }
  25. // memo对组件的缓存
  26. Child = memo(Child);
  27. function render() {
  28. ReactDOM.render(<App />, document.getElementById("root"));
  29. }
  30. render();

源码实现

useMemo和useCallback源码的实现

  1. let lastMemo;
  2. let lastMemoDependencies;
  3. function useMemo(callback, dependencies) {
  4. if (lastMemoDependencies) {
  5. let changed = dependencies.some((item, index) => {
  6. return item !== lastMemoDependencies[index];
  7. });
  8. if (changed) {
  9. lastMemo = callback();
  10. lastMemoDependencies = dependencies;
  11. }
  12. } else {
  13. lastMemo = callback();
  14. lastMemoDependencies = dependencies;
  15. }
  16. return lastMemo;
  17. }
  18. let lastCallback;
  19. let lastCallbackDependencies;
  20. function useCallback(callback, dependencies) {
  21. if (lastCallbackDependencies) {
  22. let changed = dependencies.some((item, index) => {
  23. return item !== lastCallbackDependencies[index];
  24. });
  25. if (changed) {
  26. lastCallback = callback;
  27. lastCallbackDependencies = dependencies;
  28. }
  29. } else {
  30. lastCallback = callback;
  31. lastCallbackDependencies = dependencies;
  32. }
  33. return lastCallback;
  34. }

useRef

  • 可以通过ref使用html 结构对象
  • 配合 react 做一些操作dom的行为
  • 还可以方便的保存上一个的状态值

codesandbox案例

  1. const FocusInput = () => {
  2. const [inputVal, setInputVal] = useState("hell0");
  3. const InputRef = useRef();
  4. return (
  5. <div>
  6. <input
  7. ref={InputRef}
  8. value={inputVal}
  9. onChange={(e) => setInputVal(e.target.value)}
  10. />
  11. <button onClick={() => InputRef.current.focus()}>选中输入框</button>
  12. </div>
  13. );
  14. };
  15. const StorePrevState = () => {
  16. const [count, setCount] = useState(0);
  17. const prev = useRef(null);
  18. return (
  19. <div>
  20. <p>当前值:{count}</p>
  21. <p>上个值:{prev.current}</p>
  22. <button
  23. onClick={() => {
  24. prev.current = count;
  25. setCount((count) => count + 1);
  26. }}
  27. >
  28. click add
  29. </button>
  30. </div>
  31. );
  32. };

useReducer

把很多个action进行统一规划成一个状态state

useReducer接收一个reducer函数 (state, action) => newState,并返回当前的state以及与其搭配使用的dispatch方法

  • 首先要定义一个reducer方法,供useReducer的第一个参数
  • reducer方法中,处理不同的类型
  • 通过传递不同的type值,可以进行不同结果返回

使用

  1. function reducer(state, action) {
  2. switch (action.type) {
  3. case "increment":
  4. return state + 1;
  5. case "decrement":
  6. return state - 1;
  7. default:
  8. return state;
  9. }
  10. }
  11. function App() {
  12. let [state, dispatch] = useReducer(reducer, 0);
  13. return (
  14. <div>
  15. <p>{state}</p>
  16. <button onClick={() => dispatch({ type: "increment" })}>+</button>
  17. <button onClick={() => dispatch({ type: "decrement" })}>-</button>
  18. </div>
  19. );
  20. }
  21. function render() {
  22. ReactDOM.render(<App />, document.getElementById("root"));
  23. }
  24. render();

useReducer源码实现

  1. // useReducer原理实现
  2. let lastState;
  3. function useReducer(reducer, initialState) {
  4. lastState = lastState || initialState;
  5. function dispatch(action) {
  6. // reducer函数执行后会返回一个新的处理过的数据
  7. lastState = reducer(lastState, action);
  8. render();
  9. }
  10. return [lastState, dispatch];
  11. }

useContext

  • 可以实现跨组件层级进行数据传递
  • 通过React.createContext定义一个总数据调度中心,会返回的两个对象 Provider 和 Consumer
    • Provider:用于提供数据
    • Consumer:用户接收数据
  • 在子组件中,通过是useContext获取传递过来的数据

codesandbox案例

useContext使用

  1. const CounterCtx = React.createContext();
  2. function App() {
  3. let [state, setState] = useState({ number: 0 });
  4. return (
  5. <CounterCtx.Provider value={{ state, setState }}>
  6. <Counter />
  7. </CounterCtx.Provider>
  8. );
  9. }
  10. function Counter() {
  11. // 返回的数据是 上面Provider传递的 value对象值
  12. let { state, setState } = useContext(CounterCtx);
  13. return (
  14. <div>
  15. <p>{state.number}</p>
  16. <button onClick={() => setState({ number: state.number + 1 })}>+</button>
  17. <button onClick={() => setState({ number: state.number - 1 })}>-</button>
  18. </div>
  19. );
  20. }
  21. function render() {
  22. ReactDOM.render(<App />, document.getElementById("root"));
  23. }
  24. render();

useContext源码实现

  1. function useContext(context) {
  2. return context._currentValue;
  3. }

useEffect

  • useEffect是一个Effect hook,给函数组件增加操作副作用的能力
  • 和class组件中的 componentDidMount、componentDidUpdate和componentWillUnmount具有类似用途

使用 useEffect(updateFn)

useEffect使用

  1. import React, { useState, useEffect } from "react";
  2. import ReactDOM from "react-dom";
  3. function App() {
  4. let [num, setNum] = useState(0);
  5. let [name, setName] = useState("effect");
  6. useEffect(() => {
  7. console.log("num", num);
  8. }, [num]);
  9. return (
  10. <div>
  11. <p>
  12. {name}:{num}
  13. </p>
  14. <button onClick={() => setName("hello")}>change name</button>
  15. <button onClick={() => setNum(num + 1)}>+</button>
  16. </div>
  17. );
  18. }
  19. function render() {
  20. ReactDOM.render(<App />, document.getElementById("root"));
  21. }
  22. render();

useEffect源码实现

  1. let lastDependencies;
  2. function useEffect(effect, dependencies) {
  3. if (lastDependencies) {
  4. // effect存在
  5. let changed = dependencies.some((item, index) => {
  6. return item !== lastDependencies[index];
  7. });
  8. if (changed) {
  9. // 通过宏任务执行,在dom渲染完成后执行
  10. setTimeout(effect);
  11. lastDependencies = dependencies;
  12. }
  13. } else {
  14. setTimeout(effect);
  15. lastDependencies = dependencies;
  16. }
  17. }

useLayoutEffect

  • useLayoutEffect和useEffect使用方式一样,但是它会在所有DOM变更之后同步调用effct
  • useEffect不会阻塞浏览器渲染,而useLayoutEffect 会阻塞浏览器渲染
  • useEffect 会在浏览器渲染结束后执行, 而useLayoutEffect 在Dom更新完成后,浏览器绘制前执行。

useLayoutEffect执行是在浏览器渲染前执行,执行时机更早。
useEffect执行是在浏览器渲染之后执行
image.png

useLayoutEffect使用

  1. import React, { useState, useRef, useLayoutEffect } from "react";
  2. import ReactDOM from "react-dom";
  3. function AnimationEffect() {
  4. const ref = useRef();
  5. useLayoutEffect(() => {
  6. console.log("useEffec");
  7. ref.current.style.transform = "translateX(500px)";
  8. ref.current.style.transition = "all 0.8s";
  9. });
  10. let style = {
  11. width: "100px",
  12. height: "100px",
  13. backgroundColor: "red",
  14. };
  15. console.log('render');
  16. return <div style={style} ref={ref}></div>;
  17. }
  18. function render() {
  19. ReactDOM.render(<AnimationEffect />, document.getElementById("root"));
  20. }
  21. render();

useLayoutEffect源码实现

  1. let lastLayoutDependencies;
  2. function useLayoutEffect(effect, dependencies) {
  3. if (lastLayoutDependencies) {
  4. let changed = dependencies.some((item, index) => {
  5. return item !== lastLayoutDependencies[index];
  6. });
  7. if (changed) {
  8. // 放在微任务中执行,会在DOM渲染之前执行
  9. Promise.resolve().then(effect);
  10. lastLayoutDependencies = dependencies;
  11. }
  12. } else {
  13. Promise.resolve().then(effect);
  14. lastLayoutDependencies = dependencies;
  15. }
  16. }

useMemo和React.memo区别

  • useMemo:是对数据进行缓存
  • React.memo:是高级函数,用来封装纯函数,是对函数组件进行的缓存。
    • 组件在相同 props 的情况下渲染相同的结果,可以将其包装在 React.memo 中调用,以此记忆组件渲染结果的方式来提高组件的性能。

React.memo示例

  1. const Child = ({ seconds }) => {
  2. console.log("child");
  3. return <div>child{seconds}</div>;
  4. };
  5. // React.memo可以接收第二个参数, 用来判断是否进行组件的更新
  6. const MemoChild = React.memo(Child, areEqual);
  7. function areEqual(prevProps, nextProps) {
  8. if (prevProps.seconds === nextProps.seconds) {
  9. // 上次属性和这次属性传入的值相同,则返回true,不进行更新渲染。
  10. return true;
  11. } else {
  12. // 属性不同,返回false,进行渲染更新
  13. return false;
  14. }
  15. }
  16. export default function App() {
  17. const [time, setTime] = useState(Date.now());
  18. setInterval(() => {
  19. setTime((time) => (time = Date.now()));
  20. }, 10000);
  21. return (
  22. <div className="App">
  23. <h1>Hello React.memo</h1>
  24. <h2>{time}</h2>
  25. {/* 如果直接使用 Child 则每次都会渲染*/}
  26. {/* <Child /> */}
  27. <MemoChild seconds={time} />
  28. </div>
  29. );
  30. }

useHook真实案例

实现拖拽列表