react 常见 hook 函数

2023-05-08  本文已影响0人  暴躁程序员

react 函数组件和 React Hooks 注意事项

1.  函数组件:函数组件又被称为无状态组件,因为在函数组件中无法使用 this 和生命周期、无法定义 state,函数组件数据来源是接受父组件传递的 props 参数或者 redux 数据,所以函数组件一般只能用来渲染页面,但是 react Hooks 可以让函数组件做更多的事
2.  React Hooks 是 react 16.8.0 新增特性,它可以让函数组件拥有一些类组件特性,比如定义修改 state、组合生命周期,性能优化等
3.  hook 函数只能在函数组件内部使用,不可在函数组件外或者类组件中使用
4.  只能在函数最外层调用 hook, 不要在循环,条件或嵌套函数中调用 hook

一、useState() 在函数组件中定义和修改 state

useState() 可传入任意类型参数(初始值),返回一个数组,数组中第一个值是初始值的响应式数据第二个值是修改响应式数据的方法

  1. 在 src/App.js 中定义和修改响应式数据
import { useState } from "react";

// 父组件
function App() {
  return (
    <div>
      <A title={"云想衣裳花想容"}></A>
    </div>
  );
}

// 子组件
function A(props) {
  // 定义数字类型响应式数据
  const [count, setCount] = useState(1);

  // 定义对象类型响应式数据
  const [userinfo, setUserinfo] = useState({
    name: "alias",
    age: 20,
  });

  // 定义函数类型响应式数据,通常用于接收 props 参数
  const [propsTitle, setPropsTitle] = useState(() => {
    return props.title;
  });
  return (
    <div>
      <div>
        {/* 数字 */}
        <h1>useState Number</h1>
        <p>count: {count}</p>
        <button
          onClick={() => {
            setCount(count + 1);
          }}
        >
          count ++
        </button>
        <hr />
      </div>
      <div>
        {/* 对象 */}
        <h1>useState Object</h1>
        <p>姓名:{userinfo.name}</p>
        <p>年龄:{userinfo.age}</p>
        <button
          onClick={() => {
            setUserinfo({
              ...userinfo,
              age: userinfo.age + 1,
            });
          }}
        >
          age ++
        </button>
        <hr />
      </div>
      <div>
        {/* props */}
        <h1>useState props</h1>
        <p>标题:{propsTitle}</p>
        <button
          onClick={() => {
            setPropsTitle(propsTitle + "1");
          }}
        >
          props.title ++
        </button>
        <hr />
      </div>
    </div>
  );
}

export default App;

二、useReducer() 在函数组件中定义和修改 state

使用方式类似 Redux

第一步:定义store
第二步:定义reducer
第三步:给 useReducer() 传入reducer 和 store ,返回组件状态state和操作状态的方法dispatch()
第四步:在组件 return 的模板中使用 state 渲染页面,通过dispatch()修改state
  1. 在 src/App.js 中定义和修改响应式数据
import { useReducer } from "react";

// 父组件
function App() {
  return (
    <div>
      <A title={"云想衣裳花想容"}></A>
    </div>
  );
}

// 子组件
function A(props) {
  // 1. 定义 store
  const store = {
    count: 1,
    userinfo: {
      name: "alias",
      age: 20,
    },
    propsTitle: props.title,
  };

  // 2. 定义reducer
  const reducer = (prevState, action) => {
    switch (action.type) {
      case "count":
        return { ...prevState, count: action.count };
      case "userinfo":
        return { ...prevState, userinfo: action.userinfo };
      case "propsTitle":
        return { ...prevState, propsTitle: action.propsTitle };
      default:
        return prevState;
    }
  };

  // 3. 给 useReducer() 传入reducer 和 store ,返回组件状态state和操作状态的方法dispatch()
  const [state, dispatch] = useReducer(reducer, store);

  return (
    <div>
      <div>
        <h1>count</h1>
        <p>count: {state.count}</p>
        <button
          onClick={() => {
            dispatch({ type: "count", count: state.count + 1 });
          }}
        >
          count ++
        </button>
        <hr />
      </div>
      <div>
        <h1>userinfo</h1>
        <p>姓名:{state.userinfo.name}</p>
        <p>年龄:{state.userinfo.age}</p>
        <button
          onClick={() => {
            dispatch({
              type: "userinfo",
              userinfo: {
                ...state.userinfo,
                age: state.userinfo.age + 1,
              },
            });
          }}
        >
          age ++
        </button>
        <hr />
      </div>
      <div>
        <h1>propsTitle</h1>
        <p>标题:{state.propsTitle}</p>
        <button
          onClick={() => {
            dispatch({
              type: "propsTitle",
              propsTitle: state.propsTitle + "1",
            });
          }}
        >
          props.title ++
        </button>
        <hr />
      </div>
    </div>
  );
}

export default App;

三、useEffect() 在函数组件中使用生命周期

1. useEffect() 可传入两个参数,第一个参数为回调函数,第二个参数为依赖数组
2. 组件初始化时执行所有 useEffect() 函数的第一个回调函数:相当于类组件中的 componentDidMount 生命周期
3. 组件卸载时执行所有 useEffect() 函数的第一个回调函数中 return 的函数:相当于类组件中的 componentWillUnmount 生命周期
4. 组件状态变更时根据第二个数组参数决定是否执行 useEffect() 函数的第一个回调函数:相当于类组件中的 componentDidUpdate 生命周期
当不给 useEffect() 传入第二个数组参数时,任意状态数据变更都会执行 useEffect() 函数的第一个回调函数
当给 useEffect() 传入第二个数组参数为 `[]` 时,任意状态变更都不执行 useEffect() 函数的第一个回调函数,只是初始化执行
当给 useEffect() 传入第二个数组参数为 `[state1,state2 ...]` 某些状态时,只有传入的状态变更才执行 useEffect() 函数的第一个回调函数
  1. 在 src/App.js 中,点击按钮测试
import { useState, useEffect } from "react";

// 父组件
function App() {
  const [show, setShow] = useState(true);
  return (
    <div>
      <div>{show ? <A title="云想衣裳花想容"></A> : "hello world"}</div>
      <hr />
      <button
        onClick={() => {
          setShow(!show);
        }}
      >
        是否卸载子组件
      </button>
    </div>
  );
}

// 子组件
function A(props) {
  const [count, setCount] = useState(1);
  const [userinfo, setUserinfo] = useState({
    name: "alias",
    age: 20,
  });
  const [propsTitle, setPropsTitle] = useState(() => {
    return props.title;
  });

  // 当useEffect()不传入第二个参数时:初始化和任何状态变更执行第一个回调函数参数
  useEffect(() => {
    console.log(count);
    console.log(userinfo);
    console.log(propsTitle);
    console.log("-------- no ------------");
  });

  // 当useEffect()第二个参数为[]时:只是初始化执行第一个回调函数参数,状态变更不会执行
  useEffect(() => {
    console.log(count);
    console.log(userinfo);
    console.log(propsTitle);
    console.log("---------- [] ----------");
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // 当useEffect()第二个参数为某些具体状态时:初始化和具体状态变更执行第一个回调函数参数,其他状态变更不会执行
  useEffect(() => {
    console.log(userinfo);
    console.log("---------- [userinfo] ----------");
  }, [userinfo]);

  // 当在第一个回调函数参数中return一个函数时:初始化执行第一个回调函数和当前组件卸载执行return的函数
  useEffect(() => {
    console.log(propsTitle);
    console.log("---------- [propsTitle] return () => {} ----------");
    return () => {
      console.log("组件卸载执行");
    };
  }, [propsTitle]);

  return (
    <div>
      <div>
        <h1>useState Number</h1>
        <p>count: {count}</p>
        <button
          onClick={() => {
            setCount(count + 1);
          }}
        >
          count ++
        </button>
        <hr />
      </div>
      <div>
        <h1>useState Object</h1>
        <p>姓名:{userinfo.name}</p>
        <p>年龄:{userinfo.age}</p>
        <button
          onClick={() => {
            setUserinfo({
              ...userinfo,
              age: userinfo.age + 1,
            });
          }}
        >
          age ++
        </button>
        <hr />
      </div>
      <div>
        <h1>useState props</h1>
        <p>标题:{propsTitle}</p>
        <button
          onClick={() => {
            setPropsTitle(propsTitle + "1");
          }}
        >
          props.title ++
        </button>
        <hr />
      </div>
    </div>
  );
}
export default App;

四、useRef() 在函数组件中获取 DOM 节点

  1. 在 src/App.js 中
import { useRef } from "react";

function App() {
  const elTitle = useRef(null);
  return (
    <div>
      <h1 ref={elTitle}>useRef</h1>
      <button
        onClick={() => {
          console.log(elTitle);
          console.log(elTitle.current);
          console.log(elTitle.current.innerText);
        }}
      >
        get element
      </button>
    </div>
  );
}

export default App;

五、useContext(),用于后代传参获取 Context 参数

  1. 在 src/App.js 中
import { useState, createContext, useContext, Component } from "react";
const countContext = createContext();

// 父组件
function App() {
  const [count, setCount] = useState(1);
  return (
    <div>
      <h1>APP</h1>
      <button onClick={() => setCount(count + 1)}>count +1</button>
      <hr />
      <countContext.Provider value={count}>
        <A />
      </countContext.Provider>
    </div>
  );
}

// 子组件A:获取 Context 参数方式一
class A extends Component {
  render() {
    return (
      <countContext.Consumer>
        {(count) => (
          <>
            <h1>A</h1>
            <p>count: {count}</p>
            <hr />
            <B />
          </>
        )}
      </countContext.Consumer>
    );
  }
}

// 后代组件B:获取 Context 参数方式二,通过 useContext 函数获取参数
function B() {
  const count = useContext(countContext);
  return (
    <>
      <h1>B</h1>
      <p>count: {count}</p>
      <hr />
    </>
  );
}

export default App;

六、memo() 组件记忆优化

在 react 组件中,当父组件的任意状态发生变更时,依赖父组件的所有子组件都会重新渲染,我们希望只有当父组件传递给子组件的 props 发生变更时,子组件才会重新渲染

1. 在类组件中,通过 PureComponent 或者 shouldComponentUpdate 进行优化
2. 在函数组件中使用 memo() 高阶组件进行优化: memo() 传入两个参数,第一个参数是子组件,第二个参数是回调函数,只有当第二个回调函数返回true时才会重新渲染子组件
3. 一般不传第二个参数(不传:只有当子组件依赖父组件的props发生变化才重新渲染子组件,传:状态变化满足某个条件才重新渲染子组件)
  1. 在 App.js 中
import { useState, memo } from "react";

// 父组件
function App() {
  const [count, setCount] = useState(1);
  const [msg, setMsg] = useState("云想衣裳花想容");

  return (
    <>
      <h1>App</h1>
      <div>count: {count}</div>
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        only count +1
      </button>
      <div>msg: {msg}</div>
      <button
        onClick={() => {
          setMsg(msg + "1");
        }}
      >
        only msg +1
      </button>
      <hr />
      <A msg={msg}></A>
    </>
  );
}

// 子组件
const A = memo(function (props) {
  console.log("A 组件重新渲染");
  return (
    <>
      <h1>A</h1>
      <div>props.msg: {props.msg}</div>
    </>
  );
});
export default App;

七、useMemo() 状态值记忆优化

在 react 组件中,任意状态变更都会重新执行所有用于页面渲染的计算函数,但是我们希望只有当计算函数依赖的状态发生变化时才执行计算函数

1. useMemo 优化:useMemo()返回记忆的值,传入两个参数,第一个参数是回调函数,第二个参数是依赖数组,只有当第二个依赖数组中的状态发生变化时才会执行第一个回调函数
2. 通常第二个依赖数组存放第一个回调函数中用到的所有状态 [state1,state2 ...]
3. 类似 VUE 中的 computed 计算属性,只有当组件初始化和依赖状态改变时才会重新计算
  1. 在 App.js 中
import { useState, useMemo } from "react";

// 父组件
function App() {
  const [count, setCount] = useState(1);
  const [msg, setMsg] = useState("云想衣裳花想容");

  // 任何状态变更都会重新执行 funCount()
  const funCount = () => {
    console.log("funCount count变更");
    return count * 2;
  };

  // 优化:只有当 msg 状态变化才会执行回调,否则不执行
  const memoMsg = useMemo(() => {
    console.log("memoMsg msg变更---------------------------");
    return msg + "1";
  }, [msg]);

  return (
    <>
      <h1>App</h1>
      <div>funCount: {funCount()}</div>
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        count +1
      </button>
      <div>memoMsg: {memoMsg}</div>
      <button
        onClick={() => {
          setMsg(msg + "1");
        }}
      >
        only msg +1
      </button>
      <hr />
    </>
  );
}
export default App;

八、useCallback() 函数记忆优化

在 react 组件中,当父组件给子组件传递的参数含有函数时,父组件的任意状态变更都会促使子组件重新渲染,就算用 memo() 包裹子组件也不行,但是我们希望子组件不重新渲染

1. useCallback 优化:useCallback()返回记忆的函数,传入两个参数,第一个参数是回调函数,第二个参数是依赖数组,
只有当第二个依赖数组中的状态发生变化时才会返回新函数,注意:不是记忆函数而是新函数,会促使子组件更新,与 useMemo() 相反
2. 通常第二个依赖数组是 []
  1. 在 App.js 中
import { useState, memo, useCallback } from "react";

// 父组件
function App() {
  const [count, setCount] = useState(1);
  const [msg, setMsg] = useState("云想衣裳花想容");

  const log = useCallback(() => {
    // 如果不用 useCallback 包裹,那么每次父组件任意状态变更都会刷新子组件,就是使用 memo() 包裹子组件也不行
  }, []);
  return (
    <>
      <h1>App</h1>
      <div>count: {count}</div>
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        only count +1
      </button>
      <div>msg: {msg}</div>
      <button
        onClick={() => {
          setMsg(msg + "1");
        }}
      >
        only msg +1
      </button>
      <hr />
      <A msg={msg} log={log}></A>
    </>
  );
}

// 子组件
const A = memo(function (props) {
  console.log("A 组件重新渲染");
  return (
    <>
      <h1>A</h1>
      <div>props.msg: {props.msg}</div>
    </>
  );
});
export default App;
上一篇下一篇

猜你喜欢

热点阅读