hooks api 详细demo

本文全部代码demo:https://stackblitz.com/edit/react-hooks-memo-gwv9c6?file=index.jscss

overview

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 内部的变量 => 闭包vue

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 (
    <div>
      <p>You clicked count {count} times</p>
      <button onClick={onClick}>
        Click me count
      </button>
    </div>
  );
};
复制代码
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。node

useEffect

  • React 会记录下你传给 useEffect 的这个方法,而后在进行了 DOM 更新以后调用这个方法。
  • Hooks 使用了 JavaScript 的闭包(closures)。将 useEffect 放在一个组件内部,可让咱们在 effect 中,便可得到对 count state(或其它 props)的访问。
  • useEffect 在每次 render 以后都会调用。useEffect第二个参数是一个inputs数组。 他会在每次render以前比较依赖的inputs里的每一项。若是改变才会调用useEffect。这里相似 componentDidUpdate
不须要清理的 effects

使用场景:网络请求,手动更新 DOM,打印日志react

import { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
复制代码
须要清理的 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 (
    <div>
      <p>You clicked count {count} times</p>
      <p>You clicked count2 {count2} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me count
      </button>
      <button onClick={() => setCount2(count2 + 1)}>
        Click me count2
      </button>
    </div>
  );
}
复制代码
Tip

若是你熟悉 class 组件中的生命周期方法,你能够把 useEffect Hooks 视做 componentDidMount、componentDidUpdate 和 componentWillUnmount 的结合。
若是你只想运行一次effect 并清楚它, (componentDidMount and componentDidUpdate),你能够在第二个参数里传一个空数组。可是react官方建议不要将这成为习惯,由于这样会引发bug(能够参考 => 须要清理的 effects)。api

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 (
    <div>
      <button onClick={onClick}>
        Click me  
      </button>
      <DataContext.Consumer>
        {
          value => <div>{value.foo}</div> 
          //  value  {foo: "ProviderValue"}
        }
      </DataContext.Consumer>
    </div>
  );
};
//  父组件 提供
    <p>
        <DataContext.Provider value={ProviderValue}>
          <Context />
        </DataContext.Provider>
    </p>
//  提供数据
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 会返回一个 被记忆的回调函数,这个函数只有在 数组里的其中一个值变化的时候才变化。用于优化子组件,防止没必要要的渲染的时候。相似于shouldComponentUpdate。bash

useCallback(fn, inputs) 等价于 useMemo(() => fn, inputs)
数组里的输入并非被看成参数传给后面的回调函数。确切的说:每个在回调函数里被引用的值也应该出如今后面的数组里。网络

useMemo
const memoizedValue = useMemo(()=> computedExpensiveValue(a,b),[a,b]);
复制代码

返回一个 被记忆的(Memoized)值 useMemo 只有在输入的数组里的其中一个值改变的时候会从新计算被记忆的值。 这个优化的方法能够在每次渲染的时候避免昂贵的计算。antd

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 <button onClick={onClick}>{children}</button>
})
                                                                   
const ParentComponent = () => {
  const [state, setState] = useState({ children: [1, 2, 3] })
  const { children } = state
  const onClick = () => setState({ children, foo: 'baz' })
  return (
    <div>
      {
        children.map(child => (
          <ChildComponent key={child} onClick={onClick} >
            { child }
          </ChildComponent>
        )) 
      }
    </div>
  )
}

ReactDOM.render(<ParentComponent />, 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 <button onClick={onClick}>{children}</button>
});
                                                                   
const ParentComponent = () => {
  const [state, setState] = useState({ children: [1, 2, 3] })
  const { children } = state
  const onClick = useCallback (
    ()=>setState({ children, foo: 'baz' }),
  [children]);
  return (
    <div> 
    <h1>callback demo</h1>
      {
        children.map(child => (
          <ChildComponent key={child} onClick={onClick} >
            { child }
          </ChildComponent>
        )) 
      }
    </div>
  )
}

render(<ParentComponent />, document.getElementById('callback'));
复制代码

缘由:在 class component中,咱们能够做为props传递下去的callback是在class component的原型链上,是 member function。可是 function component是不存在instance,因此不能绑定callback。这就是useCallback/useMemo 的使用。在function component中 中产生一个不随父组件从新渲染而改动(mutate)的 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 (
    <div>
      <h1>callback demo</h1>
      <div>
        <div>
          <button onClick={changeState}> changeState </button>
        </div>
        unMemoizedonClick === memoizedCallback: {String(unMemoizedonClick === memoizedCallback)}
      </div>
    </div>
  )
}

render(<ParentComponent />, 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 (
    <div>
      <div>{recieptVisible}</div>

      <div>{memoCb}</div>
      <button
        type="primary"
        onClick={() =>
          setState({ ...state, recieptVisible: recieptVisible + 1 })
        }
      >
        Open recieptVisible
      </button>

      <button type="primary" onClick={Cb}>
        Cb
      </button>

      <div>{invoinceVisible}</div>
      <button
        type="primary"
        onClick={() =>
          setState({ ...state, invoinceVisible: invoinceVisible + 1 })
        }
      >
        Open invoinceVisible
      </button>
    </div>
  );
};

ReactDOM.render(<App />, 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 (
    <>
      <input ref={inputEl} type='text' />
      <button onClick={onButtonClick}>button</button> 
    </>
  );
}
复制代码
Tip

原来 ref = {(node) => this.el = node} 把node放在回调函数里取出来更直接

useImperativeHandle

useLayoutEffect

若是要进行dom操做,配合ref就应该使用useLayoutEffect ,他会阻塞页面渲染。

tip

在一些不常见的状况下你也许须要他们同步调用(好比计算元素尺寸),咱们提供了一个单独的 useLayoutEffect 来达成这样的效果。它的 API 和 useEffect 是相同的

useDebugValue

参考

相关文章
相关标签/搜索