hooks api詳細demo

20786 ワード

本明細書のすべてのコードdemo:https://stackblitz.com/edit/react-hooks-memo-gwv9c6?file=index.js

overview

  • deep div : How do React hooks really work?
  • Basic Hooks :useStateuseEffectuseContext
  • Additional Hooks :useCallback && useMemo useRefuseImperativeHandleuseLayoutEffectuseDebugValue

  • How do React hooks really work? -- hooks closures


    原文住所:https://www.netlify.com/blog/2019/03/11/deep-dive-how-do-react-hooks-really-work/setCountは、useState内部の変数=>閉パッケージにアクセスできる関数関数を返します.
    const [count, setCount] = useState(0);
    
    // simple useState useEffect  demo
    const MyReact = (function() {
      let hooks = [],
        currentHook = 0 // array of hooks, and an iterator!
      return {
        render(Component) {
          const Comp = Component() // run effects
          Comp.render()
          currentHook = 0 // reset for next render
          return Comp
        },
        useEffect(callback, depArray) {
          const hasNoDeps = !depArray
          const deps = hooks[currentHook] // type: array | undefined
          const hasChangedDeps = deps ? !depArray.every((el, i) => el === deps[i]) : true
          if (hasNoDeps || hasChangedDeps) {
            callback()
            hooks[currentHook] = depArray
          }
          currentHook++ // done with this hook
        },
        useState(initialValue) {
          hooks[currentHook] = hooks[currentHook] || initialValue // type: any
          const setStateHookIndex = currentHook ; // for setState closure
          const setState = newState => (hooks[setStateHookIndex] = newState)
          return [hooks[currentHook++], setState]
        }
      }
    })()
    
    // in usage
    function Counter() {
      const [count, setCount] = MyReact.useState(0)
      const [text, setText] = MyReact.useState('foo') // 2nd state hook!
      MyReact.useEffect(() => {
        console.log('effect', count, text)
      }, [count, text])
      return {
        click: () => setCount(count + 1),
        type: txt => setText(txt),
        noop: () => setCount(count),
        render: () => console.log('render', { count, text })
      }
    }
    let App
    App = MyReact.render(Counter)
    // effect 0 foo
    // render {count: 0, text: 'foo'}
    App.click()
    App = MyReact.render(Counter)
    // effect 1 foo
    // render {count: 1, text: 'foo'}
    App.type('bar')
    App = MyReact.render(Counter)
    // effect 1 bar
    // render {count: 1, text: 'bar'}
    App.noop()
    App = MyReact.render(Counter)
    // // no effect run
    // render {count: 1, text: 'bar'}
    App.click()
    App = MyReact.render(Counter)
    // effect 2 bar
    // render {count: 2, text: 'bar'}
    

    useState

    import React, { useState} from 'react';
    import { render } from 'react-dom';
    export default function App() {
      const [state, setState] = useState({});
      const onClick = () => setState({click:!state.click});
      return (
        

    You clicked count {count} times

    ); };

    Tip
    シミュレーションclass setState You don’t have to use many state variables.State variables can hold objects and arrays just fine, so you can still group related data together. However, unlike this.setState in a class, updating a state variable always replaces it instead of merging it. stateを宣言するときに、配列やオブジェクトを使用することができます.classのsetStateは合併state、hooksのsetStateは置換stateです.

    useEffect

  • Reactは、あなたがuseEffectに渡したこのメソッドを記録し、DOM更新を行った後にこのメソッドを呼び出します.
  • HooksはJavaScriptのクローズドパッケージ(closures)を使用しています.useEffectをコンポーネントの内部に配置すると、effectでcount state(または他のprops)へのアクセスが得られます.
  • useEffectはrenderのたびに呼び出されます.useEffectの2番目のパラメータはinputs配列です.彼はrenderの前に依存するinputsのすべての項目を比較します.変更するとuseEffectが呼び出されます.ここではcomponentDidUpdate
  • に似ています
    クリーンアップ不要effects
    使用シーンシヨウ:ネットワークリクエスト、手動更新DOM、印刷ログ
    import { useState, useEffect } from 'react';
    
    function Example() {
      const [count, setCount] = useState(0);
    
      useEffect(() => {
        document.title = `You clicked ${count} times`;
      });
    
      return (
        

    You clicked {count} times

    ); }

    クリーンアップが必要なeffects
    import { useState, useEffect } from 'react';
    
    function FriendStatus(props) {
      const [isOnline, setIsOnline] = useState(null);
    
      function handleStatusChange(status) {
        setIsOnline(status.isOnline);
      }
    
      useEffect(() => {
        ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
        //       effect        
        return function cleanup() {
          ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
        };
      });
    
      if (isOnline === null) {
        return 'Loading...';
      }
      return isOnline ? 'Online' : 'Offline';
    }
    

    シミュレーションライフサイクルcomponentDidMount、componentDidUpdate、componentWillUnmount
    
    import React, { useState,  useEffect } from 'react';
    import { render } from 'react-dom';
    
    export default function App() {
      
      const [count, setCount] = useState(0);
      const [count2, setCount2] = useState(0);
    
      useEffect(() => {
        console.log('similar componentDidmount count')
        document.title = `You clicked count ${count} times`;
      },[]);
    
      useEffect(() => {
      //  Optimizing Performance by Skipping Effects
        console.log('similar componentDidmount/componentDidUpdate count2')
        document.title = `You clicked count2 ${count2} times`;
      },[count2]);
    
      useEffect(() => {
        return ()=>{
          console.log('similar componentWillUnmount')
        }
      });
    
      return (
        

    You clicked count {count} times

    You clicked count2 {count2} times

    ); }

    Tip
    classコンポーネントのライフサイクルメソッドを熟知している場合は、useEffect HooksをcomponentDidMount、componentDidUpdate、componentWillUnmountの結合と見なすことができます.effectを1回だけ実行してそれを明らかにしたい場合は、2番目のパラメータに空の配列を渡すことができます.しかしreact政府は、バグを引き起こすため、これを習慣にしないことを提案しています(=>クリーンアップが必要なeffectsを参照してください).

    useContext

    //        
    import React, { useState, useEffect,useContext } from 'react';
    import { render } from 'react-dom';
    import DataContext from './dataContext';
    export default function App() {
      const data = useContext(DataContext);
      console.log(data) // {foo: "ProviderValue"};
      const [state, setState] = useState({});
      const onClick = () => setState({click:!state.click});
      return (
        
    { value =>
    {value.foo}
    // value {foo: "ProviderValue"} }
    ); }; //

    // import React from 'react'; const DataContext = React.createContext({}); export default DataContext;

    Tip
    useContextはネストされたConsumerコンポーネントコールバック関数を書くことなくcontextの値を直接取得することができるuseContextはReactによって受信する.createContextが作成した戻り値は、現在のcontextのvalueを返します.取得したのは、最近のproviderが提供したcontextです.providerが提供するcontextが更新されるたびに、useContextは最後にcontext valueでrenderをトリガーします.

    useCallback && useMemo


    useCallback
    const memoizedCallback = useCallback(
    ()=>{
        doSomething(a,b)
    },
    [a,b]);
    

    記憶された(Memoized)コールバック関数を返してコールバック関数と配列を受信し、useCallbackは記憶されたコールバック関数を返します.この関数は配列の1つの値が変化したときにのみ変化します.サブアセンブリを最適化し、不要なレンダリングを防止するために使用します.shouldComponentUpdateに似ています.
    useCallback(fn,inputs)はuseMemo(()=>fn,inputs)配列に等価な入力であり,パラメータとして後のコールバック関数に渡されるものではない.正確には、コールバック関数で参照される値は、後続の配列にも表示されるべきです.
    useMemo
    const memoizedValue = useMemo(()=> computedExpensiveValue(a,b),[a,b]);
    

    記憶された(Memoized)値を返すuseMemoは、入力された配列のいずれかの値が変化したときにのみ記憶された値を再計算します.この最適化方法は、レンダリングのたびに高価な計算を回避することができる.
    Tip
    React.Componentではprops/stateが変更されると再レンダリング(re-render)がトリガーされ、親コンポーネント(parent components)が再レンダリングされるとサブコンポーネント(child components)も再レンダリングされます.class ComponentでshouldComponentUpdateはpropsを指定してcomponentsを更新するかReactを使用します.PureComponent. 下のdemoを見てください
    const ChildComponent = React.memo(({ onClick, children }) => {
      console.log('clicked!')
      return 
    })
                                                                       
    const ParentComponent = () => {
      const [state, setState] = useState({ children: [1, 2, 3] })
      const { children } = state
      const onClick = () => setState({ children, foo: 'baz' })
      return (
        
    { children.map(child => ( { child } )) }
    ) } ReactDOM.render(, document.getElementById('app'))

    buttonコンポーネントをmemoで記憶し、stateを変更するときにサブコンポーネントを再レンダリングするべきではありません.しかし、実際には、サブコンポーネントが再レンダリングされます.なぜなら、ParentComponentは再レンダリング時にonClickが新しい先端関数に再バインドされるからです.shallowEqualのstrict equalの場合,等しくないと判定されmemoが失敗する.変えてuseCallbackでいい
    import React, { useState, useCallback, useMemo } from 'react';
    import { render } from 'react-dom';
    
    
    const ChildComponent = React.memo(({ onClick, children }) => {
      console.log('callback clicked!')
      return 
    });
                                                                       
    const ParentComponent = () => {
      const [state, setState] = useState({ children: [1, 2, 3] })
      const { children } = state
      const onClick = useCallback (
        ()=>setState({ children, foo: 'baz' }),
      [children]);
      return (
        

    callback demo

    { children.map(child => ( { child } )) }
    ) } render(, document.getElementById('callback'));

    原因:class componentでは、propsとして伝えられるcallbackはclass componentのプロトタイプチェーンであり、member functionである.ただしfunction componentはinstanceが存在しないためcallbackをバインドすることはできません.これがuseCallback/useMemoの使用です.function componentでは、親コンポーネントの再レンダリングに伴って変更されないcallbackが生成されます.
    //  useCallback   cb      cb  
    import React, { useState, useCallback, useMemo } from 'react';
    import { render } from 'react-dom';
    
    const ParentComponent = () => {
      const [state, setState] = useState({ children: [1, 2, 3] });
      const { children } = state;
      //    changeState     memoizedCallback === unMemoizedonClick false
      const changeState = () => setState({ children, foo: 'baz' });
      const memoizedCallback = useCallback(changeState, []);
      const unMemoizedonClick = changeState;
      return (
        

    callback demo

    unMemoizedonClick === memoizedCallback: {String(unMemoizedonClick === memoizedCallback)}
    ) } render(, document.getElementById('memoizedCallback&&unMemoizedonClick'));
    //     dep              
    import React, {
      useEffect,
      useMemo,
      memo,
      useState,
      Fragment,
      useCallback
    } from "react";
    import ReactDOM from "react-dom";
    
    import "antd/dist/antd.css";
    import "./index.css";
    
    const App = () => {
      const [state, setState] = useState({
        recieptVisible: 1,
        invoinceVisible: 1,
        noticeVisible: false,
        selectedRows: []
      });
      const { recieptVisible, invoinceVisible } = state;
      const cbfunc = () => {
        return setState({
          ...state,
          recieptVisible: recieptVisible + 1
        });
      };
    
    
    
      //       get useMemo
      //                                
      //      vue      
      const memofunc = () => {
        return recieptVisible + invoinceVisible;
      };
    
      //  watch useCallback
      //     modal visible         cb
      const Cb = useCallback(cbfunc, [recieptVisible]);
      const memoCb = useMemo(memofunc, [recieptVisible]);
    
      return (
        
    {recieptVisible}
    {memoCb}
    {invoinceVisible}
    ); }; ReactDOM.render(, document.getElementById("container"));

    useRef

    const refContainer = useRef(initialValue);
    

    初期化時にinitialValueが返された場合{current:initialValue}コンポーネントrenderが返され、現在のコンポーネントインスタンス{current:el}が返されます.
    
    import React, { useRef } from 'react';
    import { render } from 'react-dom';
    
    export default function App() { 
      const inputEl = useRef(null);
     const onButtonClick = () => {
        console.log(inputEl,'inputEl') // {current:el}
      };
      return (
        <>
          type='text' />
           
        >
      );
    }
    

    Tip
    もとはref={(node)=>this.el=node}nodeをコールバック関数に入れて取り出したほうが直接的です

    useImperativeHandle


    useLayoutEffect


    dom操作を行う場合は、refに合わせてuseLayoutEffectを使用する必要があります.彼はページレンダリングをブロックします.
    tip
    一般的ではない場合は、要素サイズの計算などの同期呼び出しが必要かもしれませんが、このような効果を達成するために個別のuseLayoutEffectを提供しています.APIとuseEffectは同じです

    useDebugValue


    リファレンス

  • React Hooks: useCallback and useMemo
  • React Hooks API
  • 5 Ways to Convert React Class Components to Functional Components w/React Hooks

  • 転載先:https://juejin.im/post/5cb6d0356fb9a06888415962