ZhengXingchi / ZhengXingchi.github.io

Apache License 2.0
0 stars 0 forks source link

react hooks心得 #67

Open ZhengXingchi opened 4 years ago

ZhengXingchi commented 4 years ago

调试网站

codesandbox JSFiddle

三篇react hook不错的普及文

React Hooks 解析(下):进阶 React Hooks 解析(上):基础 你不知道的 useCallback 使用 useReducer 和 useCallback 解决 useEffect 依赖诚实与方法内置&外置问题 React Hooks 第一期:聊聊 useCallback

一份完整的useEffect指南 一份完整的useEffect指南(2) A Complete Guide to useEffect

未读待定 react-hooks中的一些懵逼点 未读待定 TypeScript 中使用React Hook

ZhengXingchi commented 4 years ago

useRef

useRef在每次组件渲染的时候不会发生变化,类似于指针this,其只会在属性current上面发生重新赋值。一般配合useEffect用于锁住最新的state。

可以用于封装previousprops(主要理解react的useEffect在组件渲染render之后执行) 网上的usePrevious

import { useEffect,useRef } from "react"

const usePrevious=(value)=>{
    const ref=useRef()
    useEffect(()=>{
        ref.current=value;
    },value)
    return ref.current
}

参考文献

useRef为什么可以用来封装成usePrevious? 你不知道的 useRef

ZhengXingchi commented 4 years ago

useReducer

import React, { useState, useRef, useEffect,useReducer } from "react";
import "./styles.css";

export default function ShoppingList(){
  const inputRef=useRef()
  const [items,dispatch]=useReducer((state,action)=>{
    switch(action.type){
      case 'add':
        return [
          ...state,
          {
            id:state.length,
            name:action.name
          }
        ] 

      case 'remove': 
        return state.filter((_,index)=>index!=action.index)

      case 'clear':
        return []

      default:
        return state
    }
  },[])

  function handleSubmit(event) {
    event.preventDefault();
    dispatch({
      type: 'add',
      name: inputRef.current.value
    });
    inputRef.current.value = '';
  }

  return (
    <>
      <form onSubmit={handleSubmit}>

        <input ref={inputRef} />
      </form>
      <button
        className="button is-danger"
        onClick={
          () => dispatch({ type: 'clear' })
        }>clear</button>
      <ul>
        {items.map((item, index) => (
          <li className="section" key={item.id}>{item.name}
            <button className="button" onClick={
              () => dispatch({ type: 'remove', index })
            }>X</button>
          </li>
        ))}
      </ul>
    </>
  )
}
ZhengXingchi commented 4 years ago

性能优化相关api

React.memo

React.memo 和 React.PureComponent 类似, React.PureComponent 在类组件中使用,而React.memo 在函数组件中使用

import React, { useState, useRef, useEffect,useReducer } from "react";
import "./styles.css";

const Counter =React.memo( ({ value, children }) => {
  console.log('Render: ', children)

  return (
    <div>
      {children}: {value}
    </div>
  )
})
const App = () => {
  const [count1, setCount1] = React.useState(0)
  const [count2, setCount2] = React.useState(0)

  const increaseCounter1 = () => {
    setCount1(count1 => count1 + 1)
  }

  return (
    <>
      <button onClick={increaseCounter1}>Increase counter 1</button>
      <Counter value={count1}>Counter 1</Counter>
      <Counter value={count2}>Coutner 2</Counter>
    </>
  )
}
export default App

如果不加React.memo,两个Counter都会console.log出render来。React.memo 浅层对比 prop 和 state 的方式来实现了该函数。现在开始第二个计数器将不会重新渲染了,由于 prop 没有发生改变。 在 React 应用中,当某个组件的状态发生变化时,它会以该组件为根,重新渲染整个组件子树。 如要避免不必要的子组件的重渲染,你需要在所有可能的地方使用 PureComponent,或是手动实现 shouldComponentUpdate 方法。

useMemo

useCallback 和 useMemo 参数相同,第一个参数是函数,第二个参数是依赖项的数组。主要区别是 React.useMemo 将调用 fn 函数并返回其结果,而 React.useCallback 将返回 fn 函数而不调用它。

const Me = ({girlFriendWords}) => {

    // Provided that girlFriendWords is a string

    const myReply = decideWhatToSay (girlFriendWords)

    return <p>{myReply}</p>
}

代码中计算 myReply 值,默认每次组件渲染的时候都会重新执行

const Me = ({girlFriendWords}) => {

    // Provided that girlFriendWords is a string

    const myReply = React.useMemo(() => decideWhatToSay (girlFriendWords), [girlFriendWords])

    return <p>{myReply}</p>
}

使用 React.useMemo 通过 [girlFriendWords] 作为依赖项,当依赖的值发生改变,函数才会重新执行 decideWhatToSay

useCallback

使用场景是:有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;

import React, { useMemo, useCallback } from "react"
let Counter = ({ value, children, onClick }) => {
  console.log('Render: ', children)

  return (
    <div onClick={onClick}>
      {children}: {value}
    </div>
  )
}
Counter = React.memo(Counter)

const App = () => {
  const [count1, setCount1] = React.useState(0)
  const [count2, setCount2] = React.useState(0)

  const increaseCounter1 = () => {
    setCount1(count1 => count1 + 1)
  }
  const increaseCounter2 = () => {
    setCount2(count2 => count2 + 1)
  }

  return (
    <>
      <Counter value={count1} onClick={increaseCounter1}>Counter 1</Counter>
      <Counter value={count2} onClick={increaseCounter2}>Coutner 2</Counter>
    </>
  )
}

export default App

但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。

import React, { useMemo, useCallback } from "react"
let Counter = ({ value, children, onClick }) => {
  console.log('Render: ', children)

  return (
    <div onClick={onClick}>
      {children}: {value}
    </div>
  )
}
Counter = React.memo(Counter)

const App = () => {
  const [count1, setCount1] = React.useState(0)
  const [count2, setCount2] = React.useState(0)

  const increaseCounter1 = useCallback(() => {
    setCount1(count1 => count1 + 1)
  }, [])
  const increaseCounter2 = useCallback(() => {
    setCount2(count2 => count2 + 1)
  }, [])
  console.log(increaseCounter1)

  return (
    <>
      <Counter value={count1} onClick={increaseCounter1}>Counter 1</Counter>
      <Counter value={count2} onClick={increaseCounter2}>Coutner 2</Counter>
    </>
  )
}

export default App
ZhengXingchi commented 4 years ago

useEffect与useLayoutEffect

useEffect

基本上90%的情况下,都应该用这个,这个是在render结束后,你的callback函数执行,但是不会block browser painting,算是某种异步的方式吧,但是class的componentDidMount 和componentDidUpdate是同步的,在render结束后就运行,useEffect在大部分场景下都比class的方式性能更好.

useLayoutEffect

这个是用在处理DOM的时候,当你的useEffect里面的操作需要处理DOM,并且会改变页面的样式,就需要用这个,否则可能会出现出现闪屏问题, useLayoutEffect里面的callback函数会在DOM更新完成后立即执行,但是会在浏览器进行任何绘制之前运行完成,阻塞了浏览器的绘制.

可能讲的不是很清楚,看个例子吧

import React, { useEffect, useLayoutEffect, useRef } from "react";
import TweenMax from "gsap/TweenMax";
import './index.less';

const Animate = () => {
    const REl = useRef(null);
    useEffect(() => {
        /*下面这段代码的意思是当组件加载完成后,在0秒的时间内,将方块的横坐标位置移到600px的位置*/
        TweenMax.to(REl.current, 0, {x: 600})
    }, []);
    return (
        <div className='animate'>
            <div ref={REl} className="square">square</div>
        </div>
    );
};

export default Animate;

可以清楚的看到有一个一闪而过的方块.

改成useLayoutEffect试试.可以看出,每次刷新,页面基本没变化

参考文献

useEffect和useLayoutEffect的区别

ZhengXingchi commented 4 years ago

性能优化的两个示例

[转]为什么在 React 的 Render 中使用箭头函数和 bind 会造成问题 原文转自https://zhuanlan.zhihu.com/p/29266705

如何避免render方法中的绑定或内联箭头功能