内容概要

  • React Hooks 介绍
  • React Hooks 使用
  • 自定义 Hook

一、React Hooks 介绍

React Hooks 是用来做什么的

  • 对函数组件进行增强,让函数型组件可以存储状态,可以拥有处理副作用的能力
  • 让开发者在不使用类组件的情况下,实现相同的功能

类组件的不足(Hooks 要解决的问题)
1、缺少逻辑复用机制

  • 为了复用逻辑增加无实际渲染效果的组件,增加了组件层级 显示十分臃肿
  • 增加了调试的难度以及运行效率的降低

2、类组件经常会变的很复杂难以维护

  • 将一组相干的业务逻辑拆分到多个生命周期函数中
  • 在一个生命周期函数内存在多个不相干的业务逻辑

3、类成员方法不能保证this指向的正确性

二、React Hooks 使用

Hooks 意为钩子, React Hooks 就是一堆钩子函数, React 通过这些钩子函数对函数型组件进行增强,不同的钩子函数提供了不同的功能

  • useState()
  • useEffects()
  • useReduce()
  • useRef()
  • useCallback()
  • useContext()
  • useMemo()

    1、useState()

  • 用于为函数组件引入状态

  • 函数型组件原来是不能保存状态数据的
  • useState内部是使用闭包来实现保存状态数据的
  • 使用
    • 接收唯一的参数即状态初始值,初始值可以是任意数据类型
    • 返回值为数组, 数组中存储状态值和更改状态值的方法, 方法名称约定以set开头,后面加上状态名字
    • 方法可以被调用多次,用以保存不同的状态
    • 参数可以是一个函数,函数返回什么,初始状态就是什么,函数只会被调用一次,用在初始值是动态值的情况
    • 设置状态值方法的参数可以是一个值也可以是一个函数
    • 设置状态值方法的方法本身是异步的 ```typescript const [count, setCount] = useState(0)

setCount((count) => { const newCount = count + 1 console.log(newCount) // 1 return newCount })

  1. - **补充 问:this.setState是同步还是异步**
  2. - **答: **
  3. - **在组件的生命周期或者React合成事件中,是异步**
  4. - **在setTimeout或者原生dom事件中,是同步**
  5. <a name="UbjtL"></a>
  6. #### 2、useReducer
  7. - **useReducer是另一种让函数组件保存状态的方式**
  8. ```typescript
  9. function App() {
  10. function reducer(state, action) {
  11. switch(action.type)
  12. case "increment":
  13. return state - 1;
  14. case "decremen":
  15. return state + 1;
  16. default:
  17. return state;
  18. }
  19. const [count, dispatch] = useReducer(reducer, 0)
  20. return <div>
  21. <button onClick={() => {dispatch({ type: 'decremen' })}}>-1</button>
  22. <span>{count}</span>
  23. <button onClick={() => {dispatch({ type: 'increment' })}}>-1</button>
  24. </div>
  25. }

3、useContext

  • 在跨组件层级获取数据时简化获取数据的代码 ```typescript import React, { createContext, useContext } from react; const contextCount = createContext();

function App() { return }

function Foo() { const value = useContext(contextCount); return

{value}
}

export default App;

<a name="hOWVk"></a>
#### 4、useEffect()

- **让函数型组件拥有处理副作用的能力,类似声明周期函数**
- **执行时机,可以把useEffect 看作 componentDidMount, componentDidUpdate 和 componentWillUnmount这三个声明周期的组合**
   - **useEffect(() => {} )     ===>  componentDidMount, componentDidUpdate**
   - **useEffect(() => {}, [] ) ====> componentDidMount**
   - **useEffect(() => () => {} )  ====> componentWillUnMount**
```typescript
import React, { useState, useEffect } from 'react'
function App() {
  const [ count, setCount ] = useState(0);
  // 1.count 改变的时候 都会触发
  // useEffect(() => {
  //   console.log(123) //
  // })
  // 2.页面初次加载的时候 触发一次 (组件挂载完成后执行)
  // useEffect(() => {
  //   console.log(123) 
  // },[])
  // 3. 组件 被卸载的时候 触发
  // useEffect(() => {
  //   return () => {
  //     console.log('组件被卸载了')
  //   }
  // })
  return <div>{count}</div>
}
  • useEffect 可以在一个文件中被多次调用,优点
    • 按照用途将代码进行分类(将一组相干的业务逻辑归置到了同一个副作用函数中)
    • 简化重复代码,使组件内部代码更加清晰
  • useEffect 只有指定数据发生变化时触发effect,指定数据放在第二个参数的数组中
  • useEffect 结合异步函数
    • useEffect 中的参数函数不能是异步函数, 因为useEffect 函数要返回清理资源的函数, 如果是异步函数就变成了返回Promise ```typescript

useEffect(() => { (async function() => { await axios.get(); })() })


<a name="LlJ5z"></a>
#### 5、useMemo() 返回计算后的新值

- **useMemo 的行为类似 Vue 中的计算属性, 可以监测某个值的变化, 根据变化值计算新值**
- **性能优化,会缓存计算结果, 如果监测值没有发生变化, 即使组件重新渲染, 也不会重新计算。 此行为可以有助于避免在每个渲染上进行昂贵的计算**
```typescript
import { usemMemo } from 'react';

const result = useMemo( () => {
  // 如果count 发生变化 此函数重新执行
  .....
  return result;

}, [count])

6、memo

  • 性能优化, 如果本组件中的数据没有发生变化,阻止组件更新
  • 类似组件中的PureComponent 和 shouldComponentUpdate ```typescript import React, { memo } from ‘react’;

function App() {

return

2323
}

export default memo(App);


<a name="iEUc9"></a>
#### 7、useCallback()  接收函数返回一个新的函数

- **性能优化,缓存函数, 使组件重新渲染时得到相同的函数实例**
```typescript
import React, { useCallback, useState } from 'react'

function Counter() {
   const [count, setCount] = useState(0)
   const newUseCount = useCallback((count) => { setCount( count + 1)}, [count])

   return <div>
       <span>{count}</span>
       <button onClick={ (count) => {useCount( count + 2)}}> +1 </button>
      // 通过useCallback 包装后, 上面事件调用导致count改变时 不会频繁的渲染下面的Test 组件
        // 来达到性能优化的效果
      <Test newUseCount={newUseCount} />
     </div>
}

8、useRef

  • 获取DOM 元素对象 ```typescript import React, { useRef } from ‘react’;

function App() { const username = useRef(); const handler = () => { console.log(usename) } return }


- **保存数据(跨组件周期)例如清除定时器**
   - **即使组件重新渲染, 保存的数据仍然还在, 保存的数据被更改不会触发组件重新渲染**
```typescript
import React, { useState, useRef, useEffect } from 'react'

function App() {
  const [count, setCount] = useState(0);
  const timer = useRef
  useEffect(() => {
    timer.current = setInterVal(() => {
      setCount((cout) => count + 1)
     }, 1000)
  }, [])

  stopInterVal = () => {
    clearInterval(timer.current)
  }
  return <div>
      <button onClick={() => { stopInterVal }}>停止计时器</button>
      <span>{count}</span>
    </div>
}

9、自定义Hook

目的就是实现组件之间的逻辑共享

  • 自定义 Hook 是标准的封装和共享逻辑的方式
  • 自定义 Hook 是一个函数, 其名称以 use 开头
  • 自定义 Hook 其实就是逻辑和内置 Hook 的组合 ```typescript import React, { useState, useEffect } from ‘react’ import axios from ‘axios’

// 自定义 Hook

function useGetData() { const [post, setPost] = useState({}) useEffect({ axios.get(“接口名”) .then(response => setPost(response)) }, []) return [post, setPost] }

function App() { const [post, setPost] = useState(); return

{post.title}
{post.body}
}

export default App;


- **自定义 Hook 在表单中的应用**
```typescript
import React, { useState } from 'react'

function useUpdateInput(initValue) {
  const [value, setValue] = useState(initValue)
  return {
    value,
    onChange: (event) => { setValue(event.target.value)}
  }
}

function App() {
  const useNameInput = useUpdateInput("");
  const usePassInput = useUpdateInput("");
  const submitForm = (event) => {
    event.preventDefault()
    console.log(useNameInput.value)
    console.log(usePassInput.value)
  }
  return <form onSubmit={submitForm}>
      <input type="text" name="useName" {...useNameInput} />
      <input type="text" name="password" {...usePassInput} />
      <input type="submit" />
    </form>

}

三、React 路由 Hooks

1、react-router-dom 路由提供的钩子函数

  • useHistory
  • useLocation
  • useRouteMatch
  • useParams

    import { useHistory, useLocation, useRouteMatch, useParams } from ‘react-router-dom’ 使用的时候 通过 useHistory() 调用获取


四、实现useState(原理)

import React form 'react'
import ReactDOM from 'react-dom'

let state = [];
let setters = [];
let stateIndex = 0;

function createSetter(index) {
  // 下面这个方法就是 useState的第二个参数
  return function(newValue) {
    state[index] = newValue;
    render()
  }
}
function useState(initialState) {
  state[stateIndex] = state[stateIndex] ? state[stateIndex] : initialState;
  setters.push(createSetter(stateIndex))
  let value = state[stateIndex]
  let setter = setters[stateIndex]
  stateIndex++;
  return [value, setter]
}

function render() {
    stateIndex = 0
    ReactDOM.render(<APP />, document.getElementById('root'))
}

五、实现 useEffect 钩子函数

let prevDepsAry = []
let effectIndex = 0

function useEffect(callback, depsAry) {
  // 判断callback 是不是函数
  if(Object.prototype.toString.call(callback) !== '[object Function]') 
  throw new Error('不是函数')
  // 判断第二个参数是否为空
  if( typeof depsAry === 'undefined') {
    callback();
  } else {
    // 判断despsAry 是不是数组
    if( Object.prototypr.toString.call(depsAry) !== '[object Array]')
    throw new Error('不是数组')
    // 获取上一次的状态值
    let prevDeps = prevDepsAry[effectIndex];
    // 将当前的依赖值和上一次的依赖值作对比 如果有变化 调用callback
    let hasChanged = prevDeps ? depsAry.every((dep, index) => dep === prevDeps)  : true

    if ( hasChanged) {
      callback();
    }
    // 同步依赖值
    prevDepsAry[effectIndex] = depsAry
    effectIndex++;

  }
}

六、实现useReducer 钩子函数

function useReducer(reducer, inititalState) {
  const [state, setState] = useState(inititalState)
  function dispatch(action) {
    const newState = reducer(state, action)
     setState(newState);
  }
  return [state, dispatch]
}