React Hooks早知道

栏目: 服务器 · 发布时间: 6年前

  • Example

    function Example() {
      const [count, setCount] = useState(0);
      useEffect(() => {
        document.title = `点击了 ${count} 次`;
      });
      return (
        <div>
          <p>点击了 {count} 次</p>
          <button onClick={() => setCount(count + 1)}>
            点击
          </button>
        </div>
      );
    }
    复制代码
  • React 16.8

  • No Breaking Changes

    • 可选的
    • 100% 向后兼容
    • 立即可用
  • Class 不会被移除

  • Hook 被 React 团队 期望成为未来写 React 的主要方式

  • 除了不常用的 getDerivedStateFromError 和 componentDidCatch 在 hook 中还没有被等价实现(很快会添加),其他几乎可以覆盖所有使用 Class 的情况

  • 不影响对React的理解

  • 未来 Redux connect() 和 React Router 也会使用类似 useRedux() 或 useRouter() 的自定义 Hooks,当然现在的 API 用法也是兼容的

  • 对静态类型支持更好,如 TypeScript

  • 性能更好

    • 避免了 Class 的开销
    • 重用逻辑无需高阶组件,减少层级
  • 逻辑重用

    • render props
    • 高阶组件
    • 重构,抽取通用逻辑,不用重写层级结构
  • 拥抱 function, 无 Class

  • Thinking in Hooks

  • 不能在 Class 中使用

  • 创建自己的 Hooks

    • 每一次调用都会得到一个完全孤立的状态
    • 可以在同一个组件中使用两次相同的自定义 Hook
    • 约定大于特性
      • 以 "use" 开头
        • 只用于 linter plugin
        • react 并没有依赖此来搜索 hook 或完成其他功能特性
  • Hook 让我们根据代码的作用进行拆分

    • 多次使用,粒度更小
  • Hooks 只能在 顶层 调用

    • 不能在循环,条件语句,嵌套函数中调用

    • 保证其顺序性和正确性

      • 保证每次 render 都按同样的顺序执行

      • 在多个 useState 和 useEffect 调用过程中保证 state 的正确性

      • React 依赖 Hooks 的调用顺序来确保 state 和 useState 的对应

        • 通过类似数组的方式实现,每次 render 是按数组 index 进行对应
        // ------------
        // First render
        // ------------
        useState('Mary')           // 1. Initialize the name state variable with 'Mary'
        useEffect(persistForm)     // 2. Add an effect for persisting the form
        useState('Poppins')        // 3. Initialize the surname state variable with 'Poppins'
        useEffect(updateTitle)     // 4. Add an effect for updating the title
        
        // -------------
        // Second render
        // -------------
        useState('Mary')           // 1. Read the name state variable (argument is ignored)
        useEffect(persistForm)     // 2. Replace the effect for persisting the form
        useState('Poppins')        // 3. Read the surname state variable (argument is ignored)
        useEffect(updateTitle)     // 4. Replace the effect for updating the title
        
        // ...
        复制代码
    • linter plugin 会进行验证

  • 只能在 React functions 中调用, 不能在普通的 JavaScript 函数中调用

    • React 的函数式组件
    • 自定义的 Hook
  • linter plugin

  • shouldComponentUpdate

    • React .memo wrap 一个 function component 会浅比较 props

      • 仅比较属性,因为不存在 single state object to compare
      • 第二个参数接收一个自定义的 comparison function
      • 返回 true,update 将被跳过
      const Button = React.memo((props) => {
        // your component
      });
      复制代码
    • useMemo

  • getDerivedStateFromProps

    function ScrollView({row}) {
      let [isScrollingDown, setIsScrollingDown] = useState(false);
      let [prevRow, setPrevRow] = useState(null);
    
      if (row !== prevRow) {
        // Row changed since last render. Update isScrollingDown.
        setIsScrollingDown(prevRow !== null && row > prevRow);
        setPrevRow(row);
      }
    
      return `Scrolling down: ${isScrollingDown}`;
    }
    复制代码

Hooks

  • state hook

    • useState

      const [state, setState] = useState(initialState);
      复制代码
    • 多个State,多次使用useState

      • 数组解构,赋予状态变量不同的名字

      • 在每一次渲染中以相同的顺序被调用

      • initialState 不必须是对象,实际上不鼓励是对象,根据实际数据相关性,进行分组和分离。这样也更利于之后代码重构,抽取相关逻辑成一个自定义 Hook

        //:-1:
        const [state, setState] = useState({ left: 0, top: 0, width: 100, height: 100 });
        
        //:+1:改为如下:
        const [position, setPosition] = useState({ left: 0, top: 0 });
        const [size, setSize] = useState({ width: 100, height: 100 });
        
        //重构,自定义 Hook:
        function Box() {
          const position = useWindowPosition();
          const [size, setSize] = useState({ width: 100, height: 100 });
          // ...
        }
        
        function useWindowPosition() {
          const [position, setPosition] = useState({ left: 0, top: 0 });
          useEffect(() => {
            // ...
          }, []);
          return position;
        }
        复制代码
    • state 仅在第一次 render 时被创建,之后只是修改使我们得到最新的 state

    • 无需像 useEffect 或 useCallback 那样指定 依赖列表,由 React 来保证

      • 未来,React也会移除 useEffect 和 useCallback 的依赖列表,因为这完全可以通过算法自动解决
    • 函数式更新 (Functional updates)

      • 新值是通过之前的值计算而来

      • setCount 可以接受一个函数,接受之前的 state, 返回新的 state

        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>
            </>
          );
        }
        复制代码
    • state 更新了相同的值,不会进行 render 或 触发 effect

      • Object.is() 算法,进行比较

      • useMemo

      • forceUpdate ?

        • 避免使用

        • hack

          const [ignored, forceUpdate] = useReducer(x => x + 1, 0);
          
          function handleClick() {
            forceUpdate();
          }
          复制代码
    • 懒初始化

      • useState 接受一个函数,返回 initialState,仅当初始 render 时被调用进行初始化,只调用一次

        const [state, setState] = useState(() => {
          const initialState = someExpensiveComputation(props);
          return initialState;
        });
        复制代码
  • effect hook

    • useEffect

      useEffect(
        () => {
          // side effects (获取数据、设置订阅和手动更改 React 组件中的 DOM 等)
          return () => { // 可选
            // clean up
          }
        }
       	,
        [state, ...] // 可选,仅当 state (或 ...) 改变时,effect 才重新运行
      );
      复制代码
    • 将类组件中的 componentDidMountcomponentDidUpdatecomponentWillUnmount 统一为一个 API

      • 避免逻辑分散和重复书写
        • 生命周期函数常常包含不相关的逻辑,同时相关的逻辑被拆分进不同的方法
    • 在每一次 render 后运行 effects ( 包括第一次 render )

      • React 保证每次运行 effects 之前 DOM 已经更新了
      • 在每次 render 的时候,都是新创建了一个函数传递给了 useEffect
        • 每次都是创建了新的 effect 替换之前的。
        • 每个 effect 属于一个特定的 render
        • 不用担心 effect 里 state 过期的问题
    • 拥抱闭包,在函数作用域中,可方便访问 state

    • 通过返回一个函数来 clean up

      • 添加和清理的逻辑可以彼此靠近

      • 在下次运行 effect 之前清理上一次 render 中的 effect

        • 清理不在 unmount 调用一次,而是在每次 re-render 后调用

          • 避免在缺失 componentDidUpdate 时会产生的 bugs

            //当 friend 的属性改变时,会产生依旧显示之前 friend 的在线状态的bug,尤其当 unmounting 的时候,由于 unsubscribe 一个错误的 friend id 会产生内存泄露甚至 crash
            	componentDidMount() {
                ChatAPI.subscribeToFriendStatus(
                  this.props.friend.id,
                  this.handleStatusChange
                );
              }
            
            	componentWillUnmount() {
                ChatAPI.unsubscribeFromFriendStatus(
                  this.props.friend.id,
                  this.handleStatusChange
                );
              }
            复制代码
            //需要使用 componentDidUpdate
            	componentDidMount() {
                ChatAPI.subscribeToFriendStatus(
                  this.props.friend.id,
                  this.handleStatusChange
                );
              }
            
              componentDidUpdate(prevProps) {
                // Unsubscribe 之前的 friend.id
                ChatAPI.unsubscribeFromFriendStatus(
                  prevProps.friend.id,
                  this.handleStatusChange
                );
                // Subscribe 新的 friend.id
                ChatAPI.subscribeToFriendStatus(
                  this.props.friend.id,
                  this.handleStatusChange
                );
              }
            
              componentWillUnmount() {
                ChatAPI.unsubscribeFromFriendStatus(
                  this.props.friend.id,
                  this.handleStatusChange
                );
              }
            
            复制代码
        • 可以有选择的运行 effect,从而避免性能问题

          • useEffect 的第二个参数

            useEffect(() => {
              function handleStatusChange(status) {
                setIsOnline(status.isOnline);
              }
            
              ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
              return () => {
                ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
              };
            }, [props.friend.id]); // 仅当 props.friend.id 改变时 effect 才运行
            复制代码
          • 仅在 mount 和 unmount 才运行 effect

            • 第二个参数传空数组 []
          • 错误的指定第二个参数(经常是指过少配置), 会造成得不到最新的 props 或 state 的 bug

        • 把函数放到 useEffect 里, 这样更安全

          function Example({ someProp }) {
            function doSomething() {
              console.log(someProp);
            }
          
            useEffect(() => {
              doSomething();
            }, []); // :red_circle: This is not safe (it calls `doSomething` which uses `someProp`)
          }
          复制代码
          function Example({ someProp }) {
            useEffect(() => {
              function doSomething() {
                console.log(someProp);
              }
          
              doSomething();
            }, [someProp]); // :white_check_mark: OK (our effect only uses `someProp`)
          }
          
          function Example({ someProp }) {
            useEffect(() => {
              function doSomething() {
                console.log('hello');
              }
          
              doSomething();
            }, []); // :white_check_mark: OK in this example because we don't use *any* values from component scope
          }
          复制代码
        • 不能将函数放到 effect 里

          • 确保函数中没有引用 props 或 state

          • 纯计算函数,将该函数返回结果作为 effect 的依赖

          • 用 useCallback wrap 函数 (确保函数在依赖不变的情况下,本身不变),再作为 effect 依赖

            function ProductPage({ productId }) {
              // :white_check_mark: Wrap with useCallback to avoid change on every render
              const fetchProduct = useCallback(() => {
                // ... Does something with productId ...
              }, [productId]); // :white_check_mark: All useCallback dependencies are specified
            
              return <ProductDetails fetchProduct={fetchProduct} />;
            }
            
            function ProductDetails({ fetchProduct })
              useEffect(() => {
                fetchProduct();
              }, [fetchProduct]); // :white_check_mark: All useEffect dependencies are specified
              // ...
            }
            复制代码
        • effect 依赖变动太频繁

          • 函数式更新 (Functional updates)
          function Counter() {
            const [count, setCount] = useState(0);
          
            useEffect(() => {
              const id = setInterval(() => {
                setCount(count + 1); // This effect depends on the `count` state
              }, 1000);
              return () => clearInterval(id);
            }, []); // :red_circle: Bug: `count` is not specified as a dependency
          
            return <h1>{count}</h1>;
          }
          复制代码
          function Counter() {
            const [count, setCount] = useState(0);
          
            useEffect(() => {
              const id = setInterval(() => {
                setCount(c => c + 1); // :white_check_mark: This doesn't depend on `count` variable outside. The identity of the setCount function is guaranteed to be stable so it’s safe to omit.
              }, 1000);
              return () => clearInterval(id);
            }, []); // :white_check_mark: Our effect doesn't use any variables in the component scope
          
            return <h1>{count}</h1>;
          }
          复制代码
          const BookEntryList = props => {
            const [pending, setPending] = useState(0);
            const [booksJustSaved, setBooksJustSaved] = useState([]);
          
            useEffect(() => {
              const ws = new WebSocket(webSocketAddress("/bookEntryWS"));
          
              ws.onmessage = ({ data }) => {
                let packet = JSON.parse(data);
                if (packet._messageType == "initial") {
                  setPending(packet.pending);
                } else if (packet._messageType == "bookAdded") {
                  setPending(pending - 1 || 0);
                  setBooksJustSaved([packet, ...booksJustSaved]);
                } else if (packet._messageType == "pendingBookAdded") {
                  setPending(+pending + 1 || 0);
                } else if (packet._messageType == "bookLookupFailed") {
                  setPending(pending - 1 || 0);
                  setBooksJustSaved([
                    {
                      _id: "" + new Date(),
                      title: `Failed lookup for ${packet.isbn}`,
                      success: false
                    },
                    ...booksJustSaved
                  ]);
                }
              };
              return () => {
                try {
                  ws.close();
                } catch (e) {}
              };
            }, []);
          
            //...
          };
          复制代码
          function scanReducer(state, [type, payload]) {
            switch (type) {
              case "initial":
                return { ...state, pending: payload.pending };
              case "pendingBookAdded":
                return { ...state, pending: state.pending + 1 };
              case "bookAdded":
                return {
                  ...state,
                  pending: state.pending - 1,
                  booksSaved: [payload, ...state.booksSaved]
                };
              case "bookLookupFailed":
                return {
                  ...state,
                  pending: state.pending - 1,
                  booksSaved: [
                    {
                      _id: "" + new Date(),
                      title: `Failed lookup for ${payload.isbn}`,
                      success: false
                    },
                    ...state.booksSaved
                  ]
                };
            }
            return state;
          }
          const initialState = { pending: 0, booksSaved: [] };
          
          const BookEntryList = props => {
            const [state, dispatch] = useReducer(scanReducer, initialState);
          
            useEffect(() => {
              const ws = new WebSocket(webSocketAddress("/bookEntryWS"));
          
              ws.onmessage = ({ data }) => {
                let packet = JSON.parse(data);
                dispatch([packet._messageType, packet]); // The identity of the dispatch function from useReducer is always stable
              };
              return () => {
                try {
                  ws.close();
                } catch (e) {}
              };
            }, []);
          
            //...
          };
          复制代码
        • exhaustive-deps ESLint

    • 异步请求数据

      function SearchResults() {
        const [data, setData] = useState({ hits: [] });
        const [query, setQuery] = useState('react');
      
        useEffect(() => { // 在 useEffect 里直接使用 async function 是不被允许的,因为 useEffect function 必须要返回一个清理 function 或 nothing。
          let ignore = false;
      
          async function fetchData() { //需要在 useEffect function 里使用 async function
            const result = await axios('https://hn.algolia.com/api/v1/search?query=' + query);
            if (!ignore) setData(result.data); //当组件 unmount 时,阻止其设置 state
          }
      
          fetchData();
          return () => { ignore = true; }
        }, [query]); // [query] 阻止造成循环,仅当 query 改变时,effect 才执行
      
        return (
          <>
            <input value={query} onChange={e => setQuery(e.target.value)} />
            <ul>
              {data.hits.map(item => (
                <li key={item.objectID}>
                  <a href={item.url}>{item.title}</a>
                </li>
              ))}
            </ul>
          </>
        );
      }
      复制代码
    • 关注点分离,多次使用 useEffect

      • 根据代码的作用拆分成多个 effect
    • useEffect 不会阻塞浏览器渲染

      • componentDidMountcomponentDidUpdate 会阻塞
      • 提供阻塞版本 useLayoutEffect ,来满足同步调用计算元素尺寸等问题
  • other hooks

    • useContext

    • useReducer

      • 只是对 local state 进行 redux 化,没有形成 store 和 公用的 state 树
      const [state, dispatch] = useReducer(reducer, initialArg, init);
      复制代码
      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({initialState}) {
        const [state, dispatch] = useReducer(reducer, initialState);
        return (
          <>
            Count: {state.count}
            <button onClick={() => dispatch({type: 'increment'})}>+</button>
            <button onClick={() => dispatch({type: 'decrement'})}>-</button>
          </>
        );
      }
      复制代码
      • Pass down a dispatch function
      const TodosDispatch = React.createContext(null);
      
      function TodosApp() {
        // Note: `dispatch` won't change between re-renders
          const [todos, dispatch] = useReducer(todosReducer);
      
          return (
              <TodosDispatch.Provider value={dispatch}>
                  <DeepTree todos={todos} />
              </TodosDispatch.Provider>
          );
      }
      复制代码
      function DeepChild(props) {
        // If we want to perform an action, we can get dispatch from context.
          const dispatch = useContext(TodosDispatch);
      
          function handleClick() {
              dispatch({ type: 'add', text: 'hello' });
          }
      
          return (
              <button onClick={handleClick}>Add todo</button>
          );
      }
      复制代码
    • useRef

      • 类似实例变量

        function Timer() {
          const intervalRef = useRef();
        
          useEffect(() => {
            const id = setInterval(() => {
              // ...
            });
            intervalRef.current = id; // current 可以被赋任何值,类似类中的实例变量
            return () => {
              clearInterval(intervalRef.current);
            };
          });
        
          // ...
          function handleCancelClick() {
            clearInterval(intervalRef.current);
          }
          // ...
        }
        复制代码
      • 获取 previous state

        function Counter() {
          const [count, setCount] = useState(0);
          const prevCountRef = useRef();
          useEffect(() => {
            prevCountRef.current = count;
          });
          const prevCount = prevCountRef.current;
        
          return <h1>Now: {count}, before: {prevCount}</h1>;
        }
        复制代码
      • 自定义 Hook,usePrevious

        • 可能之后会提供开箱即用的官方实现
        function Counter() {
          const [count, setCount] = useState(0);
          const prevCount = usePrevious(count); // 使用自定义的 Hook
          return <h1>Now: {count}, before: {prevCount}</h1>;
        }
        // 自定义 Hook
        function usePrevious(value) {
          const ref = useRef();
          useEffect(() => {
            ref.current = value;
          });
          return ref.current;
        }
        复制代码
      • 在一些异步回掉中,读取最新 state

        // 首先点击 Show alert, 然后点击 Click me
        function Example() {
          const [count, setCount] = useState(0);
        
          function handleAlertClick() {
            setTimeout(() => {
              alert('You clicked on: ' + count); //读取的是点击 Show alert 时的 count,不是最新值
            }, 3000);
          }
        
          return (
            <div>
              <p>You clicked {count} times</p>
              <button onClick={() => setCount(count + 1)}>
                Click me
              </button>
              <button onClick={handleAlertClick}>
                Show alert
              </button>
            </div>
          );
        }
        复制代码
        // 用 useRef 修改:
        function Example() {
          const [count, setCount] = useState(0);
          
          //使用 useRef
          const countRef = useRef();
          countRef.current = count;
          
          function handleAlertClick() {
            setTimeout(() => {
              alert('You clicked on: ' + countRef.current); //通过 ref 访问 count,读取的是最新值
            }, 3000);
          }
        
          return (
            <div>
              <p>You clicked {count} times</p>
              <button onClick={() => setCount(count + 1)}>
                Click me
              </button>
              <button onClick={handleAlertClick}>
                Show alert
              </button>
            </div>
          );
        }
        复制代码
      • 懒加载

        • useRef 不像 useState 那样接收函数

          function Image(props) {
            // :warning: IntersectionObserver is created on every render
            const ref = useRef(new IntersectionObserver(onIntersect));
            // ...
          }
          复制代码
          function Image(props) {
            const ref = useRef(null);
          
            // :white_check_mark: IntersectionObserver is created lazily once
            function getObserver() {
              let observer = ref.current;
              if (observer !== null) {
                return observer;
              }
              let newObserver = new IntersectionObserver(onIntersect);
              ref.current = newObserver;
              return newObserver;
            }
          
            // When you need it, call getObserver()
            // ...
          }
          复制代码
    • useCallback(fn, deps)

      • 返回一个带缓存的 callback

      • 仅当所需依赖 deps (数组) 中元素改变时,才执行,否则返回缓存的值

      • 等价于 useMemo(() => fn, deps)

      • exhaustive-deps ESLint

        // 使用 Callback Refs, 而不用 useRef,这样当 ref 改变时,可以获知
        function MeasureExample() {
          const [height, setHeight] = useState(0);
        
          const measuredRef = useCallback(node => {
            if (node !== null) {
              setHeight(node.getBoundingClientRect().height);
            }
          }, []); // [] 确保 ref callback 不会改变,re-renders 时,不会被重复调用执行
        
          return (
            <>
              <h1 ref={measuredRef}>Hello, world</h1>
              <h2>The above header is {Math.round(height)}px tall</h2>
            </>
          );
        }
        复制代码
        // 抽取自定义 Hook
        function MeasureExample() {
          const [rect, ref] = useClientRect();
          return (
            <>
              <h1 ref={ref}>Hello, world</h1>
              {rect !== null &&
                <h2>The above header is {Math.round(rect.height)}px tall</h2>
              }
            </>
          );
        }
        
        function useClientRect() {
          const [rect, setRect] = useState(null);
          const ref = useCallback(node => {
            if (node !== null) {
              setRect(node.getBoundingClientRect());
            }
          }, []);
          return [rect, ref];
        }
        复制代码
    • useMemo(() => fn, deps)

      • 返回一个带缓存的值,避免消耗性能的计算重复执行

        const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
        复制代码
        • 仅当 a,b 改变时,才会重新调用 computeExpensiveValue
        • 每次 render 时,传给 useMemo 的函数会执行,不要把一些副作用放到里面
      • 性能优化

        function Parent({ a, b }) {
          // Only re-rendered if `a` changes:
          const child1 = useMemo(() => <Child1 a={a} />, [a]);
          // Only re-rendered if `b` changes:
          const child2 = useMemo(() => <Child2 b={b} />, [b]);
          return (
            <>
              {child1}
              {child2}
            </>
          )
        }
        复制代码
    • useImperativeHandle(ref, createHandle, [deps])

      • 当使用 ref 来给父组件提供实例时,用来提供自定义的方法属性

      • 尽量避免使用

      • 应该与 forwardRef 一起使用

        function FancyInput(props, ref) {
          const inputRef = useRef();
          useImperativeHandle(ref, () => ({
            focus: () => {
              inputRef.current.focus();
            }
          }));
          return <input ref={inputRef} />;
        }
        FancyInput = forwardRef(FancyInput);
        复制代码
        function Parent() {
          const fancyInputRef = useRef();
          return (
            <>
              <FancyInput ref={fancyInputRef} />
              <button onClick={()=>{
                  fancyInputRef.current.focus()
              }}>Focus</button>
            </>
          )
        }
        
        复制代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Build Your Own Web Site the Right Way Using HTML & CSS

Build Your Own Web Site the Right Way Using HTML & CSS

Ian Lloyd / SitePoint / 2006-05-02 / USD 29.95

Build Your Own Website The Right Way Using HTML & CSS teaches web development from scratch, without assuming any previous knowledge of HTML, CSS or web development techniques. This book introduces you......一起来看看 《Build Your Own Web Site the Right Way Using HTML & CSS》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

MD5 加密
MD5 加密

MD5 加密工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具