# React Hooks

# Hooks的优势

  • 函数式组件无this问题
  • 自定义Hook方便复用状态逻辑
  • 副作用的关注点分离

# State Hooks

useState

# Effect Hooks

useEffect

  • componentDidMount 组件挂载之后
  • componentDidUpdate 组件更新之后
  • componentWillUnMount 组件将要卸载之前

useEffect中使用:

import React , { useState , useEffect } from 'react';

const App = () => {

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

  useEffect(() => {
    // 在这里面相当于执行了 compoentDidMount 和 componentDidUpDate  
    document.title = `count: ${count}`
  })

  return (
    <div>
      <p>i am app</p>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>ADD</button>
    </div>
  )
}
export default App;

# 通过跳过 Effect 进行性能优化

在某些情况下,每次渲染后都执行清理或者执行 effect 可能会导致性能问题。在 class 组件中,我们可以通过在 componentDidUpdate 中添加对 prevPropsprevState 的比较逻辑解决:

componentDidUpdate(prevProps, prevState) {
  if (prevState.count !== this.state.count) {
    document.title = `You clicked ${this.state.count} times`;
  }
}

在hooks中使用

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新

完整案例:

import React , { useState , useEffect } from 'react';

const App = () => {
  const [count , setCount] = useState(0);
  const [name , setName] = useState('hanzo');
    
  // 第一个effect  
  useEffect(() => {
    console.log('render count');
    document.title = `count: ${count}`
  },[count]) // 当count 发送变化的时候 才会执行 DidUpdate 内容
    
  // 在第一个effect中第二个参数 添加 count 之后,出发 setName 方法 第一个effect没有执行,因为count没有发生变化,从而不会执行更新`componentDidUpdate`

  // 第二个effect  
  useEffect(() => {
    console.log('render name');
  })
  return (
    <div>
      <p>i am app</p>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>ADD</button>
      <p>{name}</p>
      <button onClick={() => setName('')}>setName</button>
    </div>
  )
}
export default App;

# useEffect 使用 componentWillUnmount (需要清除的Effect)

案例:假若我们有一个setInterval方法来每秒钟执行一些操作,但是在页面卸载的时候需要清除掉这个定时器方法

在class组件中:

import React from 'react';

class App extends React.Component {
    this.state = {
        timer: null
    }
    // ....
    componentDidUpdate() {
        this.timer = setInterval(() => {
            console.log('1s后重复执行')
        })
    },
    componentWillUnmount() {
        // 清除定时器任务
        clearInterval(this.timer);
    }
}

在hooks中:

import React , { useState , useEffect } from 'react';
export defalut const App = () => {
    const [timer] = useState(null);
    useEffect(() => {
        timer = setInterval(() => {
            console.log('1s后重复执行')
        },1000)
        return () => {
            // 执行 componentWillUnMount
			// 清除定时器任务
            clearInterval(timer);
        }
    })
    return (
    	// .....
    )
}

# Context Hooks

useContext 的用处和 Context.Consumer 效果一样,不过 useContext用于函数式组件,Context.Consumer则用于类组件

# 使用

import React , { useState , useContext , createContext  }  from 'react';

const CountContext = createContext();
function Counter() {
  const count = useContext(CountContext)
  return (
    <h1>Counter == {count}</h1>
  )
}

function App() {  
  const [count,setCount] = useState(0)
  return (
    <div>
      <p>{count}</p>
      <button onClick={() => {setCount(count + 1)}}>Add</button>
      <CountContext.Provider value={count}>
        <Counter />
      </CountContext.Provider>
    </div>
  )
}

export default App;

# Memo Hooks

在hooks出来之前,一般组件想拥有状态,都是创建class组件的。

同时,在react中,性能优化点在于:

  1. 调用setState,就会触发组件的重新渲染,无论前后的state是否不同。
  2. 父组件更新,子组件也会自动的更新。

基于上面的,react官方给出的解决方案是:

1. `shouldComponentUpdate` 检查 props 和 state 的某值是否改变,如果没有改变,则用false来阻止更新。
2. 使用`PureComponent`。
3. 使用React.memo,但它适用于函数组件,不适用class组件。

现在hooks推出了useMemo

示例:

import React , {useState,useMemo} from 'react';

export default function Memo() {
    const [count,setCount] = useState(1);
    const [val,setVal] = useState('');
    
    function expensive() {
        console.log('compute');
        let sum = 0;
        for (let i = 0; i < count * 100; i++) {
            sum += i;
        }
        return sum;
    }
    
    return (
    	<div>
        	<h4>{count}--{val}--{expensive()}</h4>
        	<div>
                <button onClick={() => setCount(count + 1)}>+c1</button>
                <input value={val} onChange={event => setValue(event.target.value)}/>
            </div>
        </div>
    )
    
}
  1. 创建了两个state,然后通过expensive函数,执行一次昂贵的计算,拿到count对应的某个值。
  2. 无论是修改count还是val,由于组件的重新渲染,都会触发expensive的执行(能够在控制台看到,即使修改val,也会打印)
  3. 但这里的昂贵计算只依赖count的值,在val修改的时候,是没有必要再次计算的,在这种情况,可以使用useMemo,只在count的值修改时,执行expensive计算
import React , {useState,useMemo} form 'react';

export default function Memo() {
    const [count,setCount] = useState(1);
    const [val,setVal] = useState('');
    
    const expensive = useMemo(() => {
        console.log('compute');
        let sum = 0;
        for (let i = 0; i < count * 100; i++) {
			sum += i;
        }
        return sum;
    },[count]);
    
    return (
    	<div>
        	<h4>{count}-{expensive}</h4>
            {val}
            <div>
                <button onClick={() => setCount(count + 1)}>+c1</button>
                <input value={val} onChange={event => setValue(event.target.value)}/>
            </div>
        </div>
    )
}
  1. 使用useMemo来执行昂贵的计算,然后将计算值返回,并且将count作为依赖值传递进去
  2. 这样,就只会在count改变的时候触发expensive执行,在修改val的时候,返回上一次缓存的值

# useCallback

useCallback跟useMemo比较类似,但它返回的是缓存的函数

const fnA = useCallback(fnB,[a])

使用场景: 有一个父组件,其中包含子组件,子组件接受一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。

import React, { useState, useCallback, useEffect } from 'react';

function Parent() {
	const [count, setCount] = useState(1);
    const [val, setVal] = useState('');
	
    const callback = useCallback(() => {
        return count;
    },[count])
	return (
    	<div>
        	<h4>{count}</h4>
            <Child callback={callback}/>
            <div>
                <button onClick={() => setCount(count + 1)}>+</button>
                <input value={val} onChange={event => setVal(event.target.value)}/>
            </div>
        </div>
    )
}

function Child({ callback }) {
    const [count,setCount] = useState(() => callback());
    useEffect(() => {
        setCount(callback());
    },[callback]);
    return (
    	<div>{count}</div>
    )
}

# memo,useMemo,useCallback的区别

  1. memo 类似于PureCompoent作用是优化组件性能,防止组件触发重渲染
  2. memo 用于于函数组件,PureCompoentshouldComponentUpdate 用于类组件
  3. useMemo 针对于一段函数逻辑是否重复执行
  4. useEffect是在渲染之后完成的,useMemo是在渲染期间完成的。 useMemo(() => {},[]),参数如果是空数组的话,只执行一次
  5. useCallback(fn) 等价于 useMemo(() => {})

# Ref Hooks

  • 获取子组件或者DOM节点的句柄
  • 渲染周期之间共享数据的存储

# 自定义Hooks