React/Hooks

136 阅读7分钟

什么是hooks

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性

下边的useState就是一个react-hook,在其下边的是class组件

function HelloWorld(props){
  return <h1>{props.name}</h1>;
}
复制代码
import React,{useState} from 'react'

const NumCount =()=>{
  const [num,setNum]=useState(0)
  return (<div onClick={()=>setNum(num+1)}>{num}</div>)
}

export default NumCount
复制代码
import React from "react";

class NumCountClass extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
  }
  render() {
    return (
      <div 
        onClick={()=>{this.setState({count:this.state.count+1})}}
        >
        {this.state.count}
      </div>
    );
  }
}

export default NumCountClass
复制代码

React认为组件不应该是复杂的容器,最好知识数据流的管道,组合管道,数据流经过各种管道处理之后去渲染页面。组件的最佳写法应该是函数,而不是类。 在16.8之前,react里面写的函数式组件不能维护自身state,只是纯函数,用在纯UI组件上比较多。并不能完全满足业务开发需求。新增的hooks Api增加代码的可复用性,弥补无状态组件没有生命周期,没有数据管理的缺陷。这样完全可用函数式组件来代替Class组件。 React Hooks的意思就是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码「钩」进来。 React Hooks 的设计目的,就是加强函数组件,完全不使用"类",就能写出一个全功能的组件。

函数式组件比Class好在哪

  • 代码量少。
  • 不用考虑this,不需要bind this。在class中随处可见.bind(this)。但也是可用箭头函数来继承this。
  • 性能更好(有待验证),在现在浏览器上对比不是很明显。但是React团队更侧重函数式组件,曾说过要对函数式组件性能进行提升。
  • 不用考虑复杂的生命周期
  • 代码组织更清晰,类组件的业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑
  • 类组件引入复杂度更高的render Props 和高阶组件

高阶组件 (HOC)

高阶组件是一种模式,用来重用组件逻辑,并不是React的Api。HOC可以单独拿出来讲一下。浅显的举个例子,不具体深入。

export function HOC(WrappedComponent){
  
  const newProps={name:'cy'}

  return (props)=>{
    return <WrappedComponent {...props} {...newProps} />
  }
}


import {HOC} from './Hoc'
import NumCountClass from './NumCountClass'

export default HOC(NumCountClass)
复制代码

优点

  • 逻辑复用。
  • Props劫持 代表作 react-router, withRouter。
  • 控制渲染 包裹之后可对组件进行节流渲染懒加载等。

缺陷

  • HOC需要在已有组件上进行包裹或嵌套,如果过多会出现多层级。
  • HOC可以劫持Props,可随意更改,难以追溯

Hooks初始化

组件初始化过程

  • 解析组件类型,判断是Function还是class类型,然后执行对应类型的处理方法
  • 判断到当前是Function类型组件后,首先在当前组件,也就是fiberNode上进行hook的创建和挂载,将所有的hook api都挂载到全局变量dispatcher上
  • 顺序执行当前组件,每遇到一个hook api都通过next将它连接到当前fiberNode的hook链表上

更新过程

useState的挂载mountState阶段会绑定dispatchAction并作为参数返回,其实也就是使用useState时返回的setXXX。 而在dispatchAction中实际上,做了两件事

  • 创建update节点,并连接到useState的queue后面,这样每次调用dispatchAction都会在后面连接一个update节点,从而生成一个更新队列。
  • 然后根据更新任务的优先级排列任务,最后遍历整个fiber树执行更新操作。

常见的hooks

useState

const [state,setState] = useState(initialState)
复制代码
  • useState 有一个参数,该参数可传如任意类型的值或者返回任意类型值的函数。
  • useState 返回值为一个数组,数组的第一个参数为我们需要使用的 state,第二个参数为一个setter函数,可传任意类型的变量,或者一个接收 state 旧值的函数,其返回值作为 state 新值。
  • 如果数据简单可以根据数据结构将数据放在不同的useState上。
  • 如果数据结构复杂可使用useReducer

useReducer

const [state, dispatch] = useReducer(reducer, initialArg, init)
复制代码
  • useReducer 接收三个参数,第一个参数为一个 reducer 函数,第二个参数是reducer的初始值,第三个参数为可选参数,值为一个函数,可以用来惰性提供初始状态。 这意味着我们可以使用使用一个init函数来计算初始状态/值,而不是显式的提供值。如果初始值可能会不一样,这会很方便,最后会用计算的值来代替初始值。
  • reducer方法接收两个参数,一个state,另一个是action。
  • useReducer返回一个数组,一个state,一个dispatch,state是返回状态中的值,dispatch是一个可以发布更新state的方法。
import React,{useReducer} from 'react'

const NumCount =()=>{

  const [countObj,dispatch]=useReducer((state,action)=>{
    if(action.type==='add'){
      return {
        count:state.count+1
      }
    }
    if(action.type==='subtract'){
      return {
        count:state.count-1
      }
    }
  },{
    count:0
  })

  return (
    <div>
      <div>{countObj.count}</div>
      <button onClick={()=>dispatch({type:'add'})}>+</button>
      <button onClick={()=>dispatch({type:'subtract'})}>-</button>
    </div>
  )
}

export default NumCount
复制代码

使用场景

  • state是数组或对象,并且层级比较深
  • state变化很复杂,需要经过复杂的计算
  • 一个操作变更多个state

useEffect

effect(副作用):指那些没有发生在数据向视图转换过程中的逻辑,如 ajax 请求、访问原生dom 元素、本地持久化缓存、绑定/解绑事件、添加订阅、设置定时器、记录日志等。 副作用操作可以分两类:需要清除的和不需要清除的。 如果有一个需求是在组件加载之后,title会随之变化,那么改变title的这个操作就是这个组件的副作用,就可以通过useEffect实现。 可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。

import React, { useEffect } from "react"

const TitleBox=function(){

  useEffect(()=>{
    document.title='done'
  })

  return <div>title</div>
}

export default TitleBox
复制代码

useEffect的作用就是指定一个副作用函数,组件每渲染一次,该函数就自动执行一次。组件首次在网页 DOM 加载后,副作用函数也会执行。

有时候不需要每次都执行

这时候就用到了useEffect的第二个参数,第二个参数执行副作用函数执行的依赖项,只有这些依赖项更新了才会执行effect的函数。 如果传入空数组则只在组件加载进入DOM后执行一次。

useEffect(()=>{
  document.title='done'
},[])
复制代码

有时候需要清除副作用

副作用函数还可以通过返回一个函数来指定如何清除副作用,为防止内存泄露,清除函数会在组件卸载前执行。如果多次渲染,则在下一个effect之前,上一个effect就被清除

import React,{useEffect, useState} from "react"

const ClearEffectBox =()=>{
  const [count,setCount]=useState(0)

  useEffect(()=>{
    const timer=setInterval(()=>{
      setCount(count+1)
    },1000)
    
    // 如果不清除 每次更新之后就又多了一个计时器
    return ()=>{
      clearInterval(timer)
    }
  })
  return <div>
    timer:{count}
  </div>
}

export default ClearEffectBox
复制代码

自定义hooks

如果函数的名字以 use 开头,并且调用了其他的 Hook ,则就称其为一个自定义 Hook 。

useTitle

简单操作页面title hook

import { useEffect } from "react";

function useTitle(title){
  useEffect(()=>{
    document.title=title
  },[title])
}

export default useTitle


useTitle('kkkkkk')
复制代码

useDebounce

简单防抖自定义hook

import { useEffect, useRef } from 'react'

const useDebounce = (fn, ms = 1000, deps = []) => {
    let timeout = useRef()
    useEffect(() => {
        if (timeout.current) clearTimeout(timeout.current)
        timeout.current = setTimeout(() => {
            fn()
        }, ms)
    }, deps)

    const cancel = () => {
        clearTimeout(timeout.current)
        timeout = null
    }
  
    return [cancel]
  }

export default useDebounce
复制代码
import React,{useState} from "react"
import useDebounce from "./useDebounce"

function DebounceBox(){

  const [val,setVal]=useState('')
  const [disVal,setDisVal]=useState('')

  useDebounce(()=>{
    setDisVal(val)
  },1000,[val])

  return <div>
    <input onInput={(e)=>{setVal(e.target.value)}} />
    disVal:{disVal}
  </div>
}

export default DebounceBox
复制代码

小疑问

为什么每次更新都执行Effect?

此默认行为保证了一致性,避免了在 class 组件中因为没有处理更新逻辑而导致常见的 bug。

自定义hook必须以use开头吗?

必须的,这是自定义hook的规范,不这么写也能用,但是无法判断这个函数内部是否有hook调用。

有了Hook,函数式组件能完全替代Class组件吗?

我认为是可以的。之前有个项目完全使用hook+function 写的,踩一些坑,但是代码量是真的少很多。

引用:

浅谈:为啥vue和react都选择了Hooks🏂?

终于搞懂 React Hooks了!!!!!

「react进阶」一文吃透react-hooks原理

React Hooks 使用总结

作者:皮蛋君61304
链接:juejin.cn/post/715943…
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。