useState

  1. import React from 'react'
  2. export default function App() {
  3. const [n, setN] = React.useState(0)
  4. const addN = () => {
  5. setN(n+1)
  6. }
  7. return (
  8. <div className="App">
  9. <div>{n}</div>
  10. <button onClick={addN}>+1</button>
  11. </div>
  12. );
  13. }

注意事项

  1. setState不会自动合并属性

    1. const [user, setUser] = React.useState({name: 'jack', age: 16})
    2. setUser({...user, age: 19})
  2. 如果state是对象,setState应返回一个新对象,如果obj地址不变, React会认为数据没变化

  3. useState接受函数,好处是减少浏览器解析

    1. const [state, setState] = React.useState(()=>{return initialState})
  4. setState接受函数,setState更新是异步的,传回调函数保证每次setState都能更新

    1. setState(n => n + 1)

    useReducer

    useReducer践行Flux/Redux思想 ```javascript import React from ‘react’ // 1. 创建初始值initialState const initial = { n: 0 } // 2. 创建所有操作reducer(state, action) const reducer = (state, action) => { if (action.type === ‘add’) { return {n: state.n + action.number} } else if(action.type === ‘multi’) { return {n: state.n * action.number} } else { throw new Error(“unknown type”) } }

export default function App() { // 3. 将reducer和initialState传给useReducer, 得到读和写API const [state, dispatch] = React.useReducer(reducer, initial)

const addN = ()=>{ // 4. 调用写API dispatch({type: ‘ad’, number: 2}) }

return (

{state.n}
); }

  1. <a name="6AESe"></a>
  2. ### useReducer替代Redux
  3. ```javascript
  4. import React, { useEffect } from "react";
  5. // 1. 初始化store
  6. const store = {
  7. user: null,
  8. books: null,
  9. movies: null
  10. };
  11. // 2. 将所有操作集中在reducer
  12. function reducer(state, action) {
  13. switch (action.type) {
  14. case "setUser":
  15. return { ...state, user: action.user };
  16. case "setBooks":
  17. return { ...state, books: action.books };
  18. case "setMovies":
  19. return { ...state, movies: action.movies };
  20. default:
  21. throw new Error();
  22. }
  23. }
  24. // 3. 创建Context
  25. const Context = React.createContext(null);
  26. export default function App() {
  27. // 4. 用useState创建读和写的API
  28. const [state, dispatch] = React.useReducer(reducer, store);
  29. const api = { state, dispatch };
  30. return (
  31. // 5. 通过Context.Provider将第四步的内容传给各组件
  32. <Context.Provider value={api}>
  33. <User />
  34. <hr />
  35. <Books />
  36. <Movies />
  37. </Context.Provider>
  38. );
  39. }
  40. function User() {
  41. // 6. 各组件通过useContext获取读写API
  42. const { state, dispatch } = React.useContext(Context);
  43. useEffect(() => {
  44. ajax("/user").then((user) => {
  45. dispatch({ type: "setUser", user: user.name });
  46. });
  47. }, []);
  48. return (
  49. <div>
  50. <h1>个人信息</h1>
  51. <div>name: {state.user ? state.user : ""}</div>
  52. </div>
  53. );
  54. }

完整代码:https://codesandbox.io/s/sweet-feather-iglyi

组件模块化

以上例为例

  • 将组件都放到components文件夹里
  • 将ajax和Context都放到单独的文件
  • 将reducer都放到reducers文件夹

image.png

  1. export default {
  2. setUser: (state, action) => {
  3. return { ...state, user: action.user };
  4. }
  5. };
  1. const obj = {
  2. ...userReducer,
  3. ...booksReducer,
  4. ...moviesReducer
  5. };
  6. function reducer(state, action) {
  7. const fn = obj[action.type];
  8. if (fn) {
  9. return fn(state, action);
  10. } else {
  11. throw new Error();
  12. }
  13. }

完整代码:https://codesandbox.io/s/beautiful-neumann-ko4n5

useContext

Context是局部的全局变量

  1. import React from "react";
  2. function Papa() {
  3. return (
  4. <div>
  5. {" "}
  6. 我是Papa
  7. <Son />
  8. </div>
  9. );
  10. }
  11. function Son() {
  12. // 3. 通过useContext获得数据
  13. const { n, setN } = React.useContext(Context);
  14. return (
  15. <div>
  16. 我是Son
  17. <div>{n}</div>
  18. <button
  19. onClick={() => {
  20. setN(n + 1);
  21. }}
  22. >
  23. +1
  24. </button>
  25. </div>
  26. );
  27. }
  28. // 1. 创建一个Context
  29. const Context = React.createContext(null);
  30. export default function App() {
  31. const [n, setN] = React.useState(0);
  32. return (
  33. // 2. 将包含读写API的数据传给Provider内的组件
  34. <Context.Provider value={{ n, setN }}>
  35. <Papa />
  36. </Context.Provider>
  37. );
  38. }

完整代码:https://codesandbox.io/s/youthful-wildflower-wmssd

useEffect

用途:
改变外部环境,如修改document.title
模拟生命周期
参考:https://www.yuque.com/qingrenyoutiandi/grlnzp/gxnz4y

useLayoutEffect

useEffect在浏览器渲染完成之后执行
useLayoutEffect在浏览器渲染完成之前执行
useLayoutEffect总是比useEffect先执行
优先使用useEffect,有改变layout(修改DOM)才放useLayoutEffect

  1. import React, { useState, useLayoutEffect } from "react";
  2. import ReactDOM from "react-dom";
  3. const BlinkyRender = () => {
  4. const [value, setValue] = useState(0);
  5. useLayoutEffect(() => {
  6. document.querySelector('#x').innerText = `value: 1000`
  7. }, [value]);
  8. return (
  9. <div id="x" onClick={() => setValue(0)}>value: {value}</div>
  10. );
  11. };
  12. ReactDOM.render(
  13. <BlinkyRender />,
  14. document.querySelector("#root")
  15. );

useMemo和useCallback

useMemo和useCallback主要用于性能优化
React会有多余的render, 在下面的例子中,如果修改N, 子组件Child中的m并没有改变,Child函数仍然会执行

  1. import { useState } from "react";
  2. function Child(props){
  3. console.log("Child执行了")
  4. return (
  5. <>
  6. <div>{props.m}</div>
  7. </>
  8. )
  9. }
  10. export default function App() {
  11. const [n, setN] = useState(0)
  12. const [m, setM] = useState(0)
  13. const addN = ()=>{setN(n+1)}
  14. return (
  15. <div>
  16. <div>{n}</div>
  17. <button onClick={addN}>changeN</button>
  18. <div className="App">
  19. <Child m={m}/>
  20. </div>
  21. </div>
  22. );
  23. }

使用React.memo()可以使组件只有在props变化的时候才重新执行

  1. const Child2 = React.memo(Child)

可以把Child函数声明直接写到React.memo

  1. const Child = React.memo((props) => {
  2. console.log("Child执行了");
  3. return (
  4. <>
  5. <div>{props.m}</div>
  6. </>
  7. );
  8. });

但是有一个bug,添加监听函数后一秒破功。因为只要App重新渲染,addM就会重新执行,子组件Child的props就变了,所以会重新渲染

  1. import React, { useState } from "react";
  2. const Child = React.memo((props) => {
  3. console.log("Child执行了");
  4. return (
  5. <>
  6. <div>{props.m}</div>
  7. {// 添加监听函数}
  8. <button onClick={props.addM}>changM</button>
  9. </>
  10. );
  11. });
  12. export default function App() {
  13. const [n, setN] = useState(0);
  14. const [m, setM] = useState(0);
  15. const addN = () => {
  16. setN(n + 1);
  17. };
  18. const addM = () => {
  19. setM(m + 1);
  20. };
  21. return (
  22. <div>
  23. <div>{n}</div>
  24. <button onClick={addN}>changeN</button>
  25. <div className="App">
  26. <Child m={m} addM={addM} />
  27. </div>
  28. </div>
  29. );
  30. }

使用useMemo

  1. const addM = useMemo(() => {
  2. const fn = () => setM(m + 1);
  3. return fn;
  4. }, [m]);

第一个参数()=>返回一个函数或值,第二个参数为依赖
只有当依赖变化时,才计算出新的value, 如果依赖不变,则重用之前的value
useMemo语法太繁琐了,每次都要写()=>fn/value,
为了不用写useMemo(() => () => { doSomething() })
于是就有了语法糖useCallback, 第一个参数只需要写回调函数() => { doSomething() }

  1. const addM = useCallback(() => console.log("hello"), [m]);

完整代码:https://codesandbox.io/s/zealous-heisenberg-pzcn6

useRef

如果你需要一个值,在组件不断render时保持不变, 则使用useRef
初始化: const count = useRef(0)
读取: count.current

  1. import { useEffect, useRef, useState } from "react";
  2. export default function App() {
  3. const [n, setN] = useState(0);
  4. const count = useRef(0);
  5. useEffect(() => {
  6. count.current += 1;
  7. console.log(count.current);
  8. });
  9. return (
  10. <div className="App">
  11. <div>{n}</div>
  12. <button onClick={() => setN(n + 9)}>+9</button>
  13. </div>
  14. );
  15. }

由于React的理念,当ref变化时,不会自动render
监听ref, 当ref.current变化时,调用setX即可

  1. import { useEffect, useRef, useState } from "react";
  2. import "./styles.css";
  3. export default function App() {
  4. const [_, set_] = useState(null);
  5. const count = useRef(0);
  6. return (
  7. <div className="App">
  8. <div>{count.current}</div>
  9. <button
  10. onClick={() => {
  11. count.current += 1;
  12. set_(Math.random());
  13. console.log(count.current);
  14. }}
  15. >
  16. count+1
  17. </button>
  18. </div>
  19. );
  20. }

vue3的ref变化会自动render

forwardRef

props不包含ref, 不能传递ref属性,需要forwardRef实现ref的传递

  1. import React, { useEffect, useRef } from "react";
  2. import "./styles.css";
  3. const Child = React.forwardRef((props, ref) => {
  4. console.log(ref.current);
  5. return <div ref={ref}>{props.msg}</div>;
  6. });
  7. export default function App() {
  8. const ref = useRef(null);
  9. useEffect(() => {
  10. const div = ref.current; //DidMount后, ref.current指向当前DOM元素
  11. console.log(div);
  12. });
  13. return (
  14. <div className="App">
  15. <Child msg={"hello"} ref={ref} />
  16. </div>
  17. );
  18. }

useImperativeHandle

useImperativeHandle用于对 ref 的封装, 使其不引用DOM, 而是返回子组件内的数据或函数

  1. import React, {
  2. useRef,
  3. useState,
  4. useEffect,
  5. useImperativeHandle,
  6. createRef
  7. } from "react";
  8. import ReactDOM from "react-dom";
  9. import "./styles.css";
  10. function App() {
  11. const buttonRef = useRef(null);
  12. useEffect(() => {
  13. console.log(buttonRef.current);
  14. });
  15. return (
  16. <div className="App">
  17. <Button2 ref={buttonRef}>按钮</Button2>
  18. <button
  19. className="close"
  20. onClick={() => {
  21. console.log(buttonRef);
  22. buttonRef.current.x();
  23. }}
  24. >
  25. x
  26. </button>
  27. </div>
  28. );
  29. }
  30. const Button2 = React.forwardRef((props, ref) => {
  31. const realButton = useRef(null);
  32. const setRef = useImperativeHandle;
  33. setRef(ref, () => {
  34. return {
  35. x: () => {
  36. realButton.current.remove();
  37. },
  38. realButton: realButton
  39. };
  40. });
  41. return <button ref={realButton} {...props} />;
  42. });
  43. const rootElement = document.getElementById("root");
  44. ReactDOM.render(<App />, rootElement);

自定义hook

  1. import { useState, useEffect } from "react";
  2. const useList = () => {
  3. const [list, setList] = useState(null);
  4. useEffect(() => {
  5. ajax("/list").then(list => {
  6. setList(list);
  7. });
  8. }, []); // [] 确保只在第一次运行
  9. return {
  10. list: list,
  11. setList: setList
  12. };
  13. };
  14. export default useList;
  15. function ajax() {
  16. return new Promise((resolve, reject) => {
  17. setTimeout(() => {
  18. resolve([
  19. { id: 1, name: "Frank" },
  20. { id: 2, name: "Jack" },
  21. { id: 3, name: "Alice" },
  22. { id: 4, name: "Bob" }
  23. ]);
  24. }, 2000);
  25. });
  26. }

stale closure

过时的闭包
参考链接:https://dmitripavlutin.com/react-hooks-stale-closures/