React Hooks 学习笔记

React Hooks 学习笔记

文章出处: 拉 勾 大前端 高薪训练营

1. React Hooks 介绍

1.1 React Hooks 是用来做什么的

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

1.2 类组件的不足(Hooks 要解决的问题)

  1. 缺少逻辑复用的机制

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

  1. 类组件经常会变得很复杂难以维护

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

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

2. React Hooks 使用

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

2.1 useState()

用于为函数组件引入状态

import {
    
    useState} from 'react'

function App() {
    
    
  const [count, setCount] = useState(0)
  return (
    <div>
      <span>{
    
    count}</span>
      <button onClick={
    
    ()=>setCount(count+1)}> + 1</button>
    </div>
  );
}

export default App;
  1. 接受唯一的参数即状态初始值,初始值可以是任意数据类型。
const [count, setCount] = useState(0)
const [person, setPerson] = useState({
    
     name: '张三', age: 20 })
  1. 返回值为数组,数组中存储值和更改状态值的方法,方法名称约定以 set 开头,后面加上状态名称。
const [count, setCount] = useState(0)
  1. 方法可以被调用多次,用以保存不同状态值
<button onClick={
    
    ()=>setCount(count+1)}> + 1</button>
<button onClick={
    
    ()=>setPerson({
    
    name: '李四', age: 30})}>setPerson</button>
<button onClick={
    
    ()=>setPerson({
    
    ...person, name: '李四'})}>setPerson(只改变一个属性,其他属性不变)</button>
  1. 参数可以是一个函数,函数返回什么,初始状态值就是什么,函数只会被调用一次,在初始值是动态值的情况。
// 当初始值是动态值
// 这样写每次渲染都会执行
const propsCount = props.count || 0
// const [count, setCount] = useState(propsCount)

// 应该这样写
const [count, setCount] = useState(() => {
    
    
  return props.count || 0 // 只有第一次执行的时候才会执行
})
  1. 设置状态值方法的参数可以是一个值也可以是一个函数,设置状态值方法的方法本身是异步的
    如果代码依赖状态值,那要写在回调函数中:
function handleCount () {
    
    
  setCount((count) => {
    
    
    const newCount = count + 1
    document.title = newCount
    return newCount
  })
}

<button onClick={
    
    handleCount}> + 1</button>

2.2 useReducer()

useReducer 是另一种让函数组件保存状态的方式,可以将 dispatch 传给子组件使用

import {
    
     useReducer } from "react";

export default function App () {
    
    

  function reducer (state, action) {
    
    
    switch (action.type) {
    
    
      case 'increment':
        return state + 1
      case 'decrement':
        return state - 1
      default:
        return state;
    }
  }

  const [count, dispatch] = useReducer(reducer, 0)
  return (
    <div>
      <button onClick={
    
    () => dispatch({
    
    type: 'decrement'})}>-1</button>
      <span>{
    
    count}</span>
      <button onClick={
    
    () => dispatch({
    
    type: 'increment'})}>+1</button>
    </div>
  )
}

2.3 useContext()

在跨组件层级获取数据时简化获取数据的代码

import {
    
     createContext, useContext } from "react";

const countContext = createContext()

export default function App () {
    
    

  return (
    <countContext.Provider value={
    
    1000}>
      <Foo />
    </countContext.Provider>
  )
}
function Foo () {
    
    
  const value = useContext(countContext)
  return (
    <div>I am Foo {
    
    value}</div>
  )
}

2.4 useEffect()

让函数型组件拥有处理副作用的能力,类似生命周期函数

  1. useEffect 执行机制

可以把 useEffect 看做 componentDidMount, componentDidUpdate 和 componentWillUnmount 这三个函数的组合

useEffect(() => {}) => componentDidMount, componentDidUpdate
useEffect(() => {}, []) => componentDidMount
useEffect(() => () => {}) => componentDidUpdate, componentWillUnmount
useEffect(() => () => {}, []) => componentWillUnmount

import {
    
     useEffect, useState } from "react";
import ReactDOM from 'react-dom'

export default function App () {
    
    

  const [count, setCount] = useState(0)

  // 组件挂载完成之后执行,组件数据更新之后执行
  // useEffect(() => {
    
    
  //   console.log('123')
  // })

  // 组件挂载完成之后执行
  // useEffect(() => {
    
    
  //   console.log('456')
  // }, [])

  useEffect(() => {
    
    
    return () => {
    
    
      console.log('组件被卸载了')
    }
  })

  return (
    <div>
    <span>{
    
    count}</span>
    <button onClick={
    
    () => setCount(count+1)}>+1</button>
    <button onClick={
    
    () => ReactDOM.unmountComponentAtNode(document.getElementById('root'))}>卸载组件</button>
    </div>
  )
}
  1. useEffect 使用方法

(1). 为 window 对象添加滚动事件
(2). 设置定时器让 count 数值每隔一秒增加 1

import {
    
     useEffect, useState } from "react";
import ReactDOM from 'react-dom'

export default function App () {
    
    

  const [count, setCount] = useState(0)
  function onScroll () {
    
    
    console.log('页面滚动了')
  }

  useEffect(() => {
    
    
    window.addEventListener('scroll', onScroll)
    return () => {
    
    
      window.removeEventListener('scroll', onScroll)
    }
  }, [])

  useEffect(() => {
    
    
    const timerId = setInterval(() => {
    
    
      setCount(count => {
    
    
        const newCount = count + 1
        document.title = newCount
        return newCount
      })
    }, 1000);
    return () => {
    
    
      clearTimeout(timerId)
    }
  }, [])

  return (
    <div>
      <span>{
    
    count}</span>
      <button onClick={
    
    () => {
    
     ReactDOM.unmountComponentAtNode(document.getElementById('root')) }} >卸载组件</button>
    </div>
  )
}
  1. useEffect 解决的问题
    (1). 按照用途将代码进行分类(将一组相同的业务逻辑归置到了同一个副作用函数中)
    (2). 简化重复代码,是组件内部代码更加清晰

  2. 只有指定数据发生变化时触发 effect

import {
    
     useEffect, useState } from "react";

export default function App () {
    
    

  const [count, setCount] = useState(0)
  const [person, setPerson] = useState({
    
    name: '张三'})
  
  useEffect(() => {
    
    
    // person 的变化不会触发 useEffect , 因为第二个数组参数中只监听了 count
    document.title = count
    console.log(111)
  }, [count])

  return (
    <div>
      <span>{
    
    count}</span>
      <button onClick={
    
    () => setCount(count + 1)}> + 1</button>
      <button onClick={
    
    () => setPerson({
    
    name: '李四'})}>setPerson</button>
    </div>
  )
}
  1. useEffect 钩子函数结合异步函数
    useEffect 中的参数函数不能是异步函数,因为 useEffect 函数要返回清理资源的函数,如果是异步函数就变成了返回 Promise. 可以写成自执行函数的形式:
  useEffect(() => {
    
    
    (async () => {
    
    
      await axios.get()
    })()
  }, [])

2.5 useMemo()

useMemo 的行为类似 Vue 中的计算属性,可以检测某个值的变化,根据变化只计算新值。
useMemo 会缓存计算结果,如果检测子没有发生变化,及时组建重新渲染,也不会重新计算,此行为可以有助于避免在每个渲染上进行昂贵的计算。

import {
    
     useState, useMemo } from "react";

export default function App () {
    
    

  const [count, setCount] = useState(0)
  const [bool, setBool] = useState(true)
  const result = useMemo(() => {
    
    
    console.log('111') // 只有 count 改变才会重新执行这个回调函数
    return count * 2
  }, [count])

  return (
    <div>
      <span>{
    
    count} {
    
    result}</span>
      <span>{
    
    bool ? '真' : '假'}</span>
      <button onClick={
    
    () => setCount(count + 1)}> + 1</button>
      <button onClick={
    
    () => setBool(!bool)}>setBool</button>
    </div>
  )
}

2.6 memo 方法

性能优化,如果本组件中的数据没有发生变化,阻止组件更新,类似类组件中的 PureComponent 和 shouldComponentUpdate

import {
    
     memo } from 'react'

const Foo = memo(function Foo () {
    
    
  return <div>I am Foo</div>
})

2.7 useCallback()

性能优化,缓存函数,使用组件重新渲染时得到相同的函数实例。否则每次父组件渲染,函数变量的实例都会变化,导致里层组件被重新渲染

import {
    
     useState, memo, useCallback } from "react";



const Foo = memo(function Foo (props) {
    
    
  console.log('Foo 重新渲染了')
  return <div>
    <span>I am Foo</span>
    <button onClick={
    
    props.resetCount}>resetCount</button>
  </div>
})


export default function App () {
    
    

  const [count, setCount] = useState(0)
  const resetCount = useCallback(() => {
    
    
    setCount(0)
  }, [setCount])

  return (
    <div>
      <span>{
    
    count}</span>
      <button onClick={
    
    () => setCount(count + 1)}> + 1</button>
      <Foo resetCount={
    
    resetCount} />
    </div>
  )
}

2.8 useRef()

2.8.1 获取DOM元素对象
import {
    
     useRef } from "react";

export default function App () {
    
    
  const box = useRef()
  return (
    <div ref={
    
    box}>
      <button onClick={
    
    () => console.log(box)}> DIV </button>
    </div>
  )
}
2.8.2 保存数据(跨组件周期)

即使组件重新渲染,保存的数据仍然还在,保存的数据被更改不会触发组件重新渲染。

import {
    
     useRef, useState, useEffect} from "react";

export default function App () {
    
    
  const [count, setCount] = useState(0)
  let timeId = useRef() // 夸组件生命周期
  useEffect(() => {
    
    
    // 使用这个 ref 的 current 属性存储数据
    timeId.current = setInterval(() => {
    
    
      setCount(count => count + 1)
    }, 1000);
  }, [])

  const stopCount = () => {
    
    
    console.log(timeId.current)
    clearInterval(timeId.current)
  }

  const box = useRef()
  return (
    <div ref={
    
    box}>
      <span>{
    
    count}</span>
      <button onClick={
    
    () => stopCount()}> 停止 </button>
    </div>
  )
}

3. 自定义 Hook (为了在组件之间形成逻辑共享)

自定义 Hook 是标准的封装和共享逻辑的方式
自定义 Hook 是一个函数,其名称以 use 开头
自定义 Hook 其实就是逻辑和内置 Hook 的组合

如何使用自定义 Hook:

import {
    
     useState, useEffect} from "react";
import axios from "axios";

function useGetPost () {
    
    
  const [post, setPost] = useState({
    
    })
  useEffect(() => {
    
    
    axios.get('https://jsonplaceholder.typicode.com/posts/1')
    .then((response) => {
    
    
      setPost(response.data)
    })
  }, [])
  return [post, setPost]
}

export default function App () {
    
    
  const [post, setPost] = useGetPost()
  return (
    <div>
      <h1>{
    
    post.title}</h1>
      <div>{
    
    post.body}</div>
    </div>
  )
}

封装公共逻辑:

import {
    
     useState} from "react";

function useUpdateInput (initialState) {
    
    
  const [value, setValue] = useState(initialState)
  return {
    
    
    value,
    onChange: e => setValue(e.target.value)
  }
}
export default function App () {
    
    
  const usernameInput = useUpdateInput('')
  const passwordInput = useUpdateInput('')

  const submitForm = event => {
    
    
    event.preventDefault();
    console.log(usernameInput.value)
    console.log(passwordInput.value)
  }
  return (
    <form onSubmit={
    
    submitForm}>
      <input type="text" name="username" {
    
    ...usernameInput} />
      <input type="password" name="password" {
    
    ...passwordInput} />
      <input type="submit" />
    </form>
  )
}

4. React 路由 Hooks

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

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import {
    
     BrowserRouter as Router } from "react-router-dom";
import App from './App';

ReactDOM.render(
  <Router>
    <App />
  </Router>,
  document.getElementById('root')
);

App.js

import {
    
     Link, Route } from "react-router-dom";
import Home from './pages/Home'
import List from './pages/List'

export default function App () {
    
    
  
  return (
    <>
      <div>
        <Link to="/home/zhangsan">首页</Link>
        <Link to="/list">列表页</Link>
      </div>
      <div>
        <Route path="/home/:name" component={
    
    Home} />
        <Route path="/list" component={
    
    List} />
      </div>
    </>
  )
}

Home.js

import {
    
     useHistory, useLocation, useRouteMatch, useParams } from "react-router-dom";

export default function Home(props) {
    
    
  console.log(props)
  console.log(useHistory())
  console.log(useLocation())
  console.log(useRouteMatch())
  console.log(useParams())
  return  <div>
    <h1>Home works</h1>
  </div>;
}

输出结果:

{history: {…}, location: {…}, match: {…}, staticContext: undefined}
{length: 7, action: “PUSH”, location: {…}, createHref: ƒ, push: ƒ, …}
{pathname: “/home/zhangsan”, search: “”, hash: “”, state: undefined, key: “o6w5y3”}
{path: “/home/:name”, url: “/home/zhangsan”, isExact: true, params: {…}}
{name: “zhangsan”}

List.js

export default function List(props) {
    
    
  console.log(props)
  return  <div>
    <h1>List works</h1>
  </div>;
}

5. useState 钩子函数的实现原理

使用数组 state 存储状态,用数组 setters 存储 setState方法,利用闭包管理,将 下标 stateIndex 缓存在闭包中,创建 对应的 setState.

// import { useState } from 'react'
import ReactDOM from 'react-dom'
let state = []
let setters = []
let stateIndex = 0

function createSetter (index) {
    
    
  return function (newState) {
    
    
    state[index] = newState
    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')
  )
}

export default function App () {
    
    
  const [count, setCount] = useState(0)
  const [name, setName] = useState('张三')
  return <div>
    <span>{
    
    count}</span>
    <button onClick={
    
    () => setCount(count + 1)}> + 1</button>
    <span>{
    
    name}</span>
    <button onClick={
    
    () => setName('李四')}> 李四</button>
  </div>
}

6. useEffect 钩子函数的实现原理

// import { useState } from 'react'
import ReactDOM from 'react-dom'
let state = []
let setters = []
let stateIndex = 0

function createSetter (index) {
    
    
  return function (newState) {
    
    
    state[index] = newState
    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
  effectIndex = 0
  ReactDOM.render(
    <App/>,
    document.getElementById('root')
  )
}

// 上一次的依赖值
let prevDepsAry = []
let effectIndex = 0

/**
 * useEffect
 * @param {function} callback 回调函数
 * @param {Array} depsAry 依赖数组
 * @returns {function} 清理函数
 */
function useEffect (callback, depsAry) {
    
    
  if (Object.prototype.toString.call(callback) !== '[object Function]') throw new Error('useEffect 第一个参数必须是一个函数')
  if (typeof depsAry === 'undefined') {
    
    
    // 没有传递
    callback()
  } else {
    
    
    // 判断 depsAry 是不是数组
    if (Object.prototype.toString.call(depsAry) !== '[object Array]') throw new Error('useEffect 第二个参数必须是一个数组')
    // 获取上一次的状态值
    let prevDeps = prevDepsAry[effectIndex]
    // 将当前的依赖值和上一次的依赖值作对比,如果有变化,调用 callback
    let hasChanged = prevDeps ? !depsAry.every((dep, index) => dep === prevDeps[index]) : true
      // 判断值是否有变化
    if (hasChanged) {
    
    
      callback()
    }
    // 同步依赖值
    prevDepsAry[effectIndex++] = depsAry
  }
}

export default function App () {
    
    
  const [count, setCount] = useState(0)
  const [name, setName] = useState('张三')

  useEffect(() => {
    
    
    console.log('Hello')
  }, [count])

  useEffect(() => {
    
    
    console.log('World')
  }, [name])

  // 测试不传监听数据的情况
  useEffect(() => {
    
    
    console.log('xxx')
  }, [])

  return <div>
    <span>{
    
    count}</span>
    <button onClick={
    
    () => setCount(count + 1)}> + 1</button>
    <span>{
    
    name}</span>
    <button onClick={
    
    () => setName('李四')}> 李四</button>
  </div>
}

7. useReducer 钩子函数的实现原理

// import { useState } from 'react'
// import { useReducer } from 'react'
import ReactDOM from 'react-dom'
let state = []
let setters = []
let stateIndex = 0

function createSetter (index) {
    
    
  return function (newState) {
    
    
    state[index] = newState
    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
  effectIndex = 0
  ReactDOM.render(
    <App/>,
    document.getElementById('root')
  )
}

// 上一次的依赖值
let prevDepsAry = []
let effectIndex = 0

/**
 * useEffect
 * @param {function} callback 回调函数
 * @param {Array} depsAry 依赖数组
 * @returns {function} 清理函数
 */
function useEffect (callback, depsAry) {
    
    
  if (Object.prototype.toString.call(callback) !== '[object Function]') throw new Error('useEffect 第一个参数必须是一个函数')
  if (typeof depsAry === 'undefined') {
    
    
    // 没有传递
    callback()
  } else {
    
    
    // 判断 depsAry 是不是数组
    if (Object.prototype.toString.call(depsAry) !== '[object Array]') throw new Error('useEffect 第二个参数必须是一个数组')
    // 获取上一次的状态值
    let prevDeps = prevDepsAry[effectIndex]
    // 将当前的依赖值和上一次的依赖值作对比,如果有变化,调用 callback
    let hasChanged = prevDeps ? !depsAry.every((dep, index) => dep === prevDeps[index]) : true
      // 判断值是否有变化
    if (hasChanged) {
    
    
      callback()
    }
    // 同步依赖值
    prevDepsAry[effectIndex++] = depsAry
  }
}

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

export default function App () {
    
    

  function reducer(state, action) {
    
    
    switch (action.type) {
    
    
      case 'increment':
        return state + 1
      case 'decrement':
        return state - 1
      default:
        return state
    }
  }

  const [cnt, dispatch] = useReducer(reducer, 0)

  return <div>
    <div>
      <button onClick={
    
    () => dispatch({
    
    type: 'increment'})}> + 1</button>
      <span>{
    
    cnt}</span>
      <button onClick={
    
    () => dispatch({
    
    type: 'decrement'})}> - 1</button>
    </div>
  </div>
}

猜你喜欢

转载自blog.csdn.net/jal517486222/article/details/112257091