React Hooks 分享

2022-11-15 16:25:25 浏览数 (1)

目录

一,什么是Hooks

二,为什么要使用Hooks

三,React hooks

四, useState 使用及实现

五,useEffect 使用及实现

六,如何实现多个useState, useEffect(原理)

七,Hooks性能优化(项目性能优化,老生常谈,做一下补充) 

八,总结


        春节过后一直在工作之余零散的学习react,怎么说呢,来了,看过,走了,仿佛什么都没有留下,正赶上小组内需要做技术分享,nice,领了 React Hooks 技术分享题目,开始准备。因为之前是一直在用vue,所以开始接触的是react的类组件模式,相对来说便于理解(跟着b站大佬学习,141节课,20年视频),后面开始接触学习函数式组件,才发现函数式组件已经一统江山了(离了个大谱,前面白学了,在公司接手项目都是函数式写法),目前持续学习中…

一,什么是Hooks

        hooks: 钩子, React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来 。

        react hooks的诞生是为了解决react开发中遇到的问题,this的指向问题,生命周期,给函数组件扩展功能。

二,为什么要使用Hooks

要解释这个原因,首先得了解react 中两种组件模式,类式组件,函数式组件

        类式组件:

代码语言:javascript复制
class ProfilePage extends React.Component {
  showMessage = () => {
    alert('Followed '   this.props.user)
  };

  handleClick = () => {
    setTimeout(this.showMessage, 3000)
  }

  render() {
    return <button onClick={this.handleClick}>Follow</button>
  }
}

        函数式组件:

代码语言:javascript复制
function ProfilePage(props) {
  const showMessage = () => {
    alert('Followed '   props.user);
  }

  const handleClick = () => {
    setTimeout(showMessage, 3000)
  }

  return (
    <button onClick={handleClick}>Follow</button>
  )
}

        ps:通常情况下默认这两种写法相同,但是上述例子有个隐藏问题,props绑定的值不会一直更新,而this是一直是最新的,这也是class写法的弊端 

        react在v16.8.0版本推出hooks,彻底改变了react组件生态,推出hooks之前大家都写class,v16.8.0之后,函数式组件逐步一统江山。

为什么函数式组件比类式组件好呢,为什么是在推出hooks之后呢?

  • 函数式组件更加声明式,hooks使得生命周期更加底层化,与开发者无关
  • 函数式组件更加函数式,hooks拥抱了函数
  • 类式组件消耗性能比较大,因为需要创建组件的实例,而且不能销毁
  • 函数式组件消耗性能小,不需要创建实例,得到返回的react元素后就把中间量销毁
  • 函数式组件是没有状态,没有生命周期的,hooks出现解决了这一痛点

        React 的本质是能够将声明式的代码映射成命令式的DOM操作,将数据映射成可描述的UI对象。hooks更符合这一理念,因此有更广阔的发展空间。

三,React hooks

  名称及作用:

  • useState     返回有状态值,以及更新这个状态值的函数
  • useEffect     接受包含命令式,可能有副作用代码的函数
  • useContext  接受上下文对象(从react.createContext返回的值)并返回当前上下文值 
  • useReducer  useState的代替方案,接受类型为(state,action)=> newState的reducer,并返回与dispatch方法配对的当前状态
  • useCallback   返回一个回忆的memoized版本,该版本仅在其中一个输入发生更改时才会更改
  • useMemo      纯的一个记忆函数
  • useRef           返回一个可变的ref对象,其.current属性被初始化为传递的参数
  • useImperativeMethods   自定义使用ref时公开给父组件的实例值
  • useMutationEffect  更新兄弟组件之前,它在react执行其DOM改变的同一阶段同步触发
  • useLayoutEffect     DOM改变后同步触发,使用它来从DOM读取布局并同步重新渲染

        特性: 

                1,只能在顶层调用Hooks,不要在循环,条件或嵌套函数中调用Hook                 2,不要在普通的JavaScript中使用Hooks                 3,除了useRef,其他hooks 均存在Capture Value 特性

初学者掌握 useState,useEffect,useRef  这三个hooks就可以上手开发了,下面我也围绕着这几个hooks逐一展开(useRef 与 vue ref 大致相同,故忽略,有需要的小伙伴可查找官网API)

四, useState 使用及实现

      使用方法:

  • 让函数组件可以有state状态,并进行状态的读写操作
  • 语法: const [xxx, setXxx]  =  useState(initValue)
  • useState() 说明:
    1. 参数:第一次初始化指定的值在内部作缓存
    2. 返回值: 包括两个元素的数组,第一个为内部当前状态值,第二个为更新状态值的函数
  • setXxx()两种写法:
    1. setXxx(newValue) : 参数为非函数值,直接指定新的状态值,内部用其覆盖原来的状态值
    2. setXxx(value => newValue): 参数为函数,接受原来的状态值,返回新的状态值,内部用其覆盖原来的状态值

eg:

代码语言:javascript复制
import { Component, useState } from 'react';

// 类式组件
// class App extends Component {
//   state = { count: 0 }
//   add = () => {
//     this.setState(state => ({count: state.count   1}))
//   }
//   render() {
//     return (
//       <div>
//         <h2>当前和为{this.state.count}</h2>
//         <button onClick={this.add}>点我 1</button>
//       </div>
//     )
//   }
// }

// 函数式组件
function App () {
  const [count, setCount] = useState(0)
  function add () {
    // setCount(count  1)
    setCount(count => count   1)  // 函数式写法
  }
  return (
    <div>
      <h2>当前和为{count}</h2>
      <button onClick={add}>点我 1</button>
    </div>
  )
}
export default App;

 手动实现一个简单useState

useState实现功能并不复杂,初始化赋值,返回一个函数改变状态

代码语言:javascript复制
import { render } from 'react-dom'

let _state   // 把 state 存储在外面
 
function useMyState(initialValue) {
  _state = _state || initialValue  // 如果没有 _state,说明是第一次执行,把 initialValue 复制给它
  function setState(newState) {
    _state = newState
    render()
  }
  return [_state, setState]
}

export default useMyState

 这样模拟了一个简单的useState,并不能使用它,可以思考一下,当有多个状态需要初始化的时候该怎么处理,这个下面再探讨

五,useEffect 使用及实现

 使用方法:

  • 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
  • React中的副作用操作
    1. 发ajax请求获取数据
    2. 设置订阅 / 启动定时器
    3. 手动更改真实DOM
  • 语法说明

        useEffect(() => {

                // 在此可以执行任何带副作用操作

                return () => {

                        // 在此做一下收尾工作,比如清除定时器,取消订阅等

                }

           }, [stateValue])  // 如果指定的是 [ ] ,回调函数只会在第一次render()后执行

  • 可以把 useEffect 看做如下三个函数的组合
    1. componentDidMount()
    2. componentDidUpdate()
    3. componentWillmount()

 eg:

代码语言:javascript复制
import { Component, useEffect, useState } from 'react';
import ReactDOM  from 'react-dom';

// 类式组件
// class App extends Component {
//   state = { count: 0 }
//   add = () => {
//     this.setState(state => ({count: state.count   1}))
//   }
//   unmounted = () => {
//     ReactDOM.unmountComponentAtNode(document.getElementById('root'))
//   }
//   componentDidMount () {
//     this.timer = setInterval(() => {
//       this.setState(state => ({count: state.count   1}))
//     }, 1000)
//   }
//   componentWillUnmount () {
//     clearInterval(this.timer)
//   }
//   render() {
//     return (
//       <div>
//         <h2>当前和为{this.state.count}</h2>
//         <button onClick={this.add}>点我 1</button>
//         <button onClick={this.unmounted}>卸载组件</button>
//       </div>
//     )
//   }
// }

// 函数式组件
function App () {
  const [count, setCount] = useState(0)
  useEffect(() => {
    let timer = setInterval(() => {
      setCount(count => count  1)
    }, 1000)
    return () => {
      clearInterval(timer)
    }
  },[])
  function add () {
    setCount(count => count  1)
  }
  function unmounted () {
    ReactDOM.unmountComponentAtNode(document.getElementById('root'))
  }
  return (
    <div>
      <h2>当前和为{count}</h2>
      <button onClick={add}>点我 1</button>
      <button onClick={unmounted}>卸载组件</button>
    </div>
  )
}
export default App;

同理实现一个简单useEffect

代码语言:javascript复制
let _deps // _deps 记录 useEffect 上一次的 依赖

function useMyEffect(callback, depArray) {
  const hasNoDeps = !depArray // 如果 dependencies 不存在
  const hasChangedDeps = _deps
    ? !depArray.every((el, i) => el === _deps[i]) // 两次的dependencies 是否完全相等
    : true
  /* 如果 dependencies 不存在,或者 dependencies 有变化*/
  if (hasNoDeps || hasChangedDeps) {
    callback()
    _deps = depArray
  }
}

export default useMyEffect

六,如何实现多个useState, useEffect(原理)

        上面我们已经简单实现了useState,useEffect 这两个hooks,但是只能使用一次,如果声明多个,_state, _deps会被覆盖,React 底层是通过单链表来实现的,这也导致了 hooks的一些特性,如只能在函数最外层调用hooks,不能在循环、条件判断、子函数中调用,Capture Value等等

        模拟底层实现: 

代码语言:javascript复制
let memoizedState = []; // hooks 存放在这个数组
let cursor = 0; // 当前 memoizedState 下标
 
function useState(initialValue) {
  memoizedState[cursor] = memoizedState[cursor] 
    || initialValue;
  const currentCursor = cursor;
  function setState(newState) {
    memoizedState[currentCursor] = newState;
    render();
  }
  return [memoizedState[cursor  ], setState]; 
  // 返回当前 state,并把 cursor 加 1
}
 
 
function useEffect(callback, depArray) {
  const hasNoDeps = !depArray;
  const deps = memoizedState[cursor];
  const hasChangedDeps = deps
    ? !depArray.every((el, i) => el === deps[i])
    : true;
  
  if (hasNoDeps || hasChangedDeps) {
    callback();
    memoizedState[cursor] = depArray;
  }
  
  cursor  ;
}

        模拟实现图例说明

1,初始化

2,初次渲染

3,事件触发

4,re-render

hooks流程小结:

Q:为什么只能在函数最外层调用 Hook?为什么不要在循环、条件判断或者子函数中调用?

A:memoizedState 数组是按hook定义的顺序来放置数据的,如果 hook 顺序变化,memoizedState 并不会感知到。

Q:自定义的 Hook 是如何影响使用它的函数组件的?

A:共享同一个 memoizedState,共享同一个顺序。

Q:"Capture Value" 特性是如何产生的?

A:每一次 ReRender 的时候,都是重新去执行函数组件了,对于之前已经执行过的函数组件,并不会做任何操作。(本质上是形成了闭包,useRef是一个普通对象,所以不存在Capture Value,可以通过这个useRef绕开这个特性)

七,Hooks性能优化(项目性能优化,老生常谈,做一下补充) 

        性能优化是前端项目,网站,框架等等绕不开的坎,正是我们一直追求的高性能不断推进着前端技术的发展。在react中我们知道,当父组件发生改变,子组件一定会重新渲染,即使所依赖的prop值未发生变化。

        在类组件中,我们可以通过shouldComponentUpdate增加逻辑来判断是否更新,但是函数式组件没有生命周期,这意味着函数式组件每一次调用都会执行其中所有逻辑,这样会带来非常大的性能损耗,因此hooks给我们提供了这两个api:useMemo、 useCallback

        老规矩,使用方法:接收两个参数,第一个是回调,第二个为依赖数据

代码语言:javascript复制
// useMemo
const memoizedValue = useMemo(
() => computeExpensiveValue(a, b), [a, b])

// useCallback
const memoizedCallback = useCallback(
  () => {
    doSomething(a, b)
  },
  [a, b],
)

// useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

相同作用:仅仅是依赖发生改变,才会重新计算结果

两者区别:

  • useMemo:计算结果是reture回来的值,主要用于缓存计算结果的值
  • useCallback: 计算结果是函数,主要用于缓存函数

参考下面例子便于理解概念

代码语言:javascript复制
import React from 'react'
  
export default function TestMemo() {
    const [count, setCount] = useState(1)
    const [val, setValue] = useState('')
 
    function expensive() {
        console.log('compute')
        let sum = 0
        for (let i = 0; i < count * 100; i  ) {
            sum  = i
        }
        return sum
    }

 //   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}-{val}-{expensive()}</h4>
  {/*    <h4>{count}-{val}-{expensive}</h4>   */}
        <div>
          <button onClick={() => setCount(count   1)}> 1</button>
          <input value={val} onChange={event => setValue(event.target.value)}/>
        </div>
    </div>
}

        useMemo简单实现,useCallback同理可得,感兴趣可以在网上找找

代码语言:javascript复制
let hookStates = []  // 保存状态的数组
let hookIndex = 0  // 索引
function useMemo(factory, dependencies) {
  if (hookStates[hookIndex]) {   // 说明不是第一次
    let [lastMemo, lastDependencies] = hookStates[hookIndex]
    // 判断一下新的依赖数组中的每一项是否跟上次完全相等
    let same = dependencies.every((item, index) => item === lastDependencies[index])
    // 没变则用老的
    if (same) {
      hookIndex  
      return lastMemo
    } else {   // 只要有一个依赖变量不一样的话
      let newMemo = factory()
      hookStates[hookIndex  ] = [newMemo, dependencies]
      return newMemo
    }
  } else { // 说明是第一次渲染 
    let newMemo = factory()
    hookStates[hookIndex  ] = [newMemo, dependencies]
    return newMemo
  }
}

        简单介绍了一下关于react 官方针对hooks优化提供的api,可以作为我们优化项目的工具,而工作中大部分的性能优化还是对于代码结构的优化,从设计的合理性,组件的提取拆分从而配合hooks 特性,api去完成优化,不可同一而论。

        比如,开发一个大型页面,需要初始化十几个甚至更多的状态,我们每多写一个useState,组件需要多执行一次render(函数式组件相比于类组件),这时候可读性就会很差,需要通过逻辑为导向,抽离在不同的文件中,再通过useMemo来屏蔽来自其他的state改变导致的Re-render等等,来降低代码的耦合性和复杂性,相信谁也不愿看到一个文件2000 行的恐怖代码。

八,总结

        在写这篇分享之前,断断续续了解react,对于 react hooks 的概念是,很强很难很酷,是react高手进阶之法,通过这段时间的学习,遇到问题,解决问题,去查找实现原理,再回过头来看,自己所掌握的还是略显浅薄,就当做自己的一篇学习笔记,给初学者一点建议,共同学习,共同成长


        以上是本次分享内容,由于是初学,查了很多技术前辈博客,借鉴学习了很多,按照自己学习思路整理在一起,因为是技术分享,主要还是现场讲解,文档只是辅助,因为时间有限,所以直接把分享文档放上来了,如果读起来不是很通畅,大家勿怪,2022,一起冲!!!

我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=224k7uaw4hqi

0 人点赞