React hooks
第一节、hooks 的出现
Section titled “第一节、hooks 的出现”
Hook是React 16.8的新增特性,对于hooks 的出现对于react是一个开创性的功能.
一、函数组件和类组件的问题
Section titled “一、函数组件和类组件的问题”函数组件有自己的优势,就是代码量少,使用方便简单,开发快速,逻辑清晰。但是也会有自己的问题
- 函数组件没有自己的状态管理
- 函数组件没有自己的声明周期
- this 也不能指向组件实例
对于函数组件的一些限制,在以前的开发中大多数都会选在class组件进行开发,但是类组件也有自己的缺点
- 当业务逻辑很多的时候,一个class 组件会变得越来越复杂,由于react 的开发模式是很难进行拆封的
- 可以是使用高阶组件进行拆分,但是过多的高阶组件,在同一个class中出现,也是很难维护,阅读性差
- 还有事件监听 this 的指向问题,会让人很不理解
- 设计上也是比较困难
- 类组件:功能全面,业务逻辑过多,很难拆分代码混乱,难以维护
- 函数组件:逻辑清晰,使用简单代码量少,之能用于渲染页面
二、hooks介绍
Section titled “二、hooks介绍”hooks 的出现就是解决这个问题的,可以让我们在不编写class的情况下使用state以及其他的React特性
- 用来弥补函数组件以前的一些缺陷,提高开发效率
- Hook的出现基本可以代替我们之前所有使用class组件的地方
- 而且 hooks 在react 16.8 就已经使用了,是完全可选,100%向后兼容的,可以渐进式的使用它
- 重点理解:(个人理解)之所以叫
hook的原因,应该是,给某个业务逻辑钩入一些额外的功能特性,因此命名
hooks 的使用要点
Section titled “hooks 的使用要点”Hook 就是 JavaScript 函数,这个函数可以帮助你 钩入(hook into) React State以及生命周期等特性;
-
Hook只能在函数组件中使用,不能在类组件,或者函数组件之外的地方使用; -
hook只能在当前作用域的顶层作用域使用不要在循环、条件判断或者子函数中调用。
-
也可以在自定义
hook中顶层使用,自定义hook 就是使用 use 开头的函数
第二节、常见的hooks
Section titled “第二节、常见的hooks”一、useState
Section titled “一、useState”
useState来自react,需要从react中导入,它是一个hook
useState定义一个state变量,useState是一种新方法,它与 class 里面的 this.state 提供的功能 “完全相同”。
-
参数:
any类型 ,初始化值,如果不设置为undefined;useState接受唯一一个参数,类似ref -
返回值:
Array类型,包含两个元素通过数组的解构,来完成赋值会非常方便
-
元素一| 值:当前状态的值(第一调用为初始化值);
函数执行完之后,
state中的变量会被React保留。 -
元素二| 函数:设置状态值的函数;
-
-
重点: 当调用 索引1 的元素,
Section titled “重点: 当调用 索引1 的元素, setValue 函数的时候,函数组件会重新调用进行渲染。”setValue函数的时候,函数组件会重新调用进行渲染。
FAQ:常见问题
Section titled “FAQ:常见问题”-
为什么
hooks都要是use开头,而不是create因为 state 属性只有组件首次渲染的时候,会进行创建,之后状态的保存都是交给
react来处理的
渲染完组件的时候发生一些额外的作用
二、useEffect 和 useLayoutEffect
Section titled “二、useEffect 和 useLayoutEffect”
Effect副作用,给使用hooks的组件,在更新DOM的时候添加一些附带的作用
-
完成一些类似于
class中生命周期的功能,- 进行异步请求
- 事件总线的监听和取消监听
- redux 的取消订阅
-
由于同一个函数组件中,可以存放多个
useEffect函数,因此useEffect所能做的要多于声明周期函数将多个业务逻辑进行区分处理,提过可读性
useEffect/useLayoutEffect 的使用
Section titled “useEffect/useLayoutEffect 的使用”-
执行时机:绘制之后的情况下,它也能保证在任何新的渲染前启动。React 在开始新的更新前,总会** **刷新之前的渲染的 effect
-
参数一:
function类型-
返回值 function 类型
Section titled “返回值 function 类型”回调函数 => 组件被 “重新渲染” 或者组件 “卸载”的时候执行
// 负责告知react, 在执行完当前组件渲染之后要执行的副作用代码useEffect(() => {// 1.监听事件// const unubscribe = store.subscribe(() => {// })// function foo() {// }// eventBus.on("why", foo)console.log("监听redux中数据变化, 监听eventBus中的why事件")// 返回值: 回调函数 => 组件被重新渲染或者组件卸载的时候执行return () => {console.log("取消监听redux中数据变化, 取消监听eventBus中的why事件")}}) -
-
参数二:
Array类型,参数一函数所依赖的值。- 如果传入存在依赖的数组时,第一次页面渲染完之后会调用一次,之后每当依赖的值发生变化,参数函数会重新调用
useEffect/useLayoutEffect都会重复调用- 参数依赖为空数组的话,参数函数只会在,组件绘制(没有渲染)完成之后执行一次
传入空数组的情况下,相当于
mount和unmount-
useEffect同mount挂载之后 执行时机一致 -
useLayoutEffect同unmount挂载前 执行时机一致
-
而参数二不传值的情况下,”每次“ 当前组件重新
render的时候都会执行-
注意:重点,如果useEffect中更新了组件会出现出现了无限循环
因为本身就是每次渲染的时候执行,在里面有放入更新代码的逻辑所以会出现无限循环
-
-
返回值:function 类型
Section titled “返回值:function 类型”-
会在执行当前
effect之前对上一个effect进行清除。 -
当前组件卸载的时候,
useEffect/useLayoutEffect的返回致函数都会执行
-
const About = memo(() => {
const [counter, setCounter] = useState(0) useEffect(() => { console.log("About渲染") return () => { console.log("About销毁") } }, [counter])
useEffect(() => { console.log("first") dispatch(findHouseInfo()) //防止出现警告 }, [dispatch])
function trigger(num) { //useEffect 依赖的counter 发生了变化,会重新执行 回调函数的 setCounter(counter + num) }
return ( <div> <h4>About</h4> <div className="info"> <p>{counter}</p> <button onClick={e => trigger(1)}>trigger</button> </div> </div> )})三、useRef
Section titled “三、useRef”- 特点(重点):
useRef返回一个ref对象,返回的ref对象在组件的整个生命周期内保持不变。
useRef使用场景
Section titled “useRef使用场景”-
用法一:通过
ref属性获取,react组件实例,或者原生domimport React, { memo, useRef } from 'react'const App = memo(() => {const titleRef = useRef()const inputRef = useRef()function showTitleDom() {console.log(titleRef.current)inputRef.current.focus()}return (<div><h2 ref={titleRef}>Hello World</h2><input type="text" ref={inputRef} /><button onClick={showTitleDom}>查看title的dom</button></div>)})export default App -
用法二:保存一个数据,这个对象在整个生命周期中可以保存不变;
-
解决闭包陷阱
四、useCallBack
Section titled “四、useCallBack”
useCallback实际的目的是为了进行性能的优化
-
重点(主要的作用):当需要将一个函数传递给子组件时, 最好使用
useCallback进行优化, 将优化之后的函数, 传递给子组件。避免重复渲染,但是会有问题,下面就解决方案 -
特性:
useCallback接收一个函数,会返回一个优化后的函数的memoized(有记忆的值)重点理解:简单来说就是,当
useCallback执行多次的时候,只会 根据 第一次 传入参数创建一个函数对象。只有所依赖的值发生变化之后,才会更新优化的函数。
- 同时
useCallBack的特性,也为其本身带来了很重要的作用
- 同时
-
-
如果没有传第二个参数的话,和正常的函数没有区别
-
当第二个参数存在依赖的话,当依赖被改变的时候,更新参数一函数
-
当第二个参数为空数组的情况下,会形成闭包多次重复执行的话都是参数一函数
就是没有依赖需要监听改变,返回的函数也不会改变
-
-
参数一:
function类型,需要优化的函数 -
参数二:
Array类型,函数做依赖的值const Counter = memo(() => {const [counter, setCounter] = useState(0)const trigger = useCallback((num) => {setCounter(counter + num)//只有参数二,数组中包含的依赖值发生了变化的时候,才会更新闭包函数}, [counter])//如果是空数组的话,多次执行返回的函数,都会是第一次记忆的函数return (<div><h4>Counter</h4><div className="box"><div className="content">{counter}</div><div className="ctrl"></div><About trigger = {trigger}/></div></div>)})
1、useCallBack 存在的问题
Section titled “1、useCallBack 存在的问题”- 问题:尽管
useCallBack记忆了第一次传入的函数,但是并没有阻止子函数多次渲染
原因:如果需要counter 更新,就必须要添加依赖,依赖变化的话,就会返回新的函数对象,这样子组件进行函数浅层比较的时候,依然会发生更新
2、结合 useRef 实现优化
Section titled “2、结合 useRef 实现优化”
useRef()特点:useRef()hooks多次调用的话,返回的是相同的引用
-
利用
useRef这一个特性,可以不用给useCallBack传入,依赖值让它一直是闭包的状态即可const Counter = memo(() => {const [counter, setCounter] = useState(0)const counterRef = useRef()//将counter 保存引用counterRef.current = counterconst trigger = useCallback((num) => {//这里重新set 的时候,上面 counterRef 会一直递增setCounter(counterRef.current + num)}, [])return (<div><h4>Counter</h4><div className="box"><div className="content">{counter}</div><div className="ctrl"></div><About trigger = {trigger}/></div></div>)})
2.1、总结
Section titled “2.1、总结”-
**
useCallBack只有将优化有的函数传递给子组件,**才会有性能优化使用
useCallback的目的就是为了不让子组件进行多次渲染 -
并不是为了缓存该函数
Section titled “并不是为了缓存该函数”
2.2、作用
Section titled “2.2、作用”- 根据
useCallBack的特性,当子组件有 “引用” 父组件的函数的时候,父组件发生了刷新,如果没有记忆的话,每次传入子组件的函数对象都是新创建的,之后在进行SCU的时候会直接将子组件直接刷新。 - 反之如果有记忆的话,当
state没有发生变化的情况下,就不会重复刷新子组件。
五、useMemo
Section titled “五、useMemo”
useMemo实际的目的也是为了进行性能的优化。
useMemo也会返回一个有记忆的值。- 在依赖不变的情况下,函数组件多次渲染执行的时候,保存的都是同一个值!
1、useMemo 和 useCallBack 的区别
Section titled “1、useMemo 和 useCallBack 的区别”-
记忆的值不同
Section titled “记忆的值不同”useCallBack记忆的是传入的函数useMemo记忆的是返回值
-
应用场景不同
Section titled “应用场景不同”-
useCallBack用于优化传入子组件的函数 -
Section titled “useMemo 重点”useMemo重点- 用于优化传入子组件的对象值,原始数据类型没有区别
- 当组件当中存在复杂耗时的业务逻辑的时候,使用
useMemo可以将结果记忆,避免重复计算
-
import React, { memo } from 'react'import { useState } from 'react'import { useMemo } from 'react'
function numCalc(num) { let result = null console.log("重新计算") for (let i = 0; i < num; i++) { result += i } return result}
const Counter = memo(() => {
/* const result = numCalc(100) */ const result = useMemo(() => { return numCalc(100) }, [])
const [count, setCounter] = useState(0)
return ( <div> <h3>Counter</h3> <div className="info"> {result} </div> <div className="ctrl"> {count} <button onClick={e => setCounter(count + 1)}>trigger</button> </div> </div> )})
export default Counter六、useReudcer
Section titled “六、useReudcer”
useReducer仅仅是useState的一种替代方案
-
用法同
Section titled “用法同 redux 的 reducer 相似”redux的reducer相似 -
在某些场景下,如果
state的处理逻辑比较复杂,针对多种情况进行区分的时候可以通过useReducer来对其进行拆分 -
参数一:
reducer函数 -
参数二:初始化
state -
返回值:同样标准
hooks返回的数组格式,index0:stateindex1:action
import React, { memo } from 'react'import { useReducer } from 'react'
function counterReucer(state, action) { switch(action.type) { case "add": return {...state, counter: action.counter + state.counter} case "sub": return {...state, counter: action.counter + state.counter} default : return state }}
const Counter = memo(() => { const [state, dispatch] = useReducer(counterReucer, {counter: 0}) console.log(state) function add() {
const action = { type: "add", counter: 1 } dispatch(action)
}
function sub() {
const action = { type: "sub", counter: -1 } dispatch(action)
}
return ( <div> <h3>Counter</h3> <div className="info"> {state.counter} <div className="ctrl"> <button onClick={e => sub()}>sub</button> <button onClick={e => add()}>add</button> </div> </div> </div> )})
export default Counter七、useContext
Section titled “七、useContext”
useCountex可以直接获取context的value,不需要进行嵌套
-
类组件使用
Context,使用多个Context的时候存在大量的嵌套 -
参数:
Context实例
import React, { memo } from 'react'import { useContext } from 'react'import {CounterCtx} from '../context/counterCtx'
const Counter = memo(() => {
const counter = useContext(CounterCtx) console.log(counter)
return ( <div> <h3>Counter</h3> <div className="info"> kjjj </div> </div> )})
export default Counter8、useImperativeHandle
Section titled “8、useImperativeHandle”主要用于 “限制” dom操作,只允许调用该
ref引用暴露出来的方法
-
参数一:父组件转发的
ref -
参数二:
function类型,返回值(通常返回一个对象进行属性或者方法的暴露)会 “赋值到” 参数一ref.current属性 -
一般情况下不会使用
import React, { memo, useImperativeHandle, useRef, forwardRef } from 'react'//接收父组件传入的refconst About = memo(forwardRef((props, ref) => { //这里手动给当前组件的dom节点进行绑定 const inputRef = useRef() //进行限制,第一个参数父组件的ref useImperativeHandle(ref, () => ({ //父组件只允许通过ref 调用这一个方法 obtain() { //这里的是子组件的ref inputRef.current.focus() } }))
return ( <div> <h2>About</h2> <input ref={inputRef} type="text" /> </div> )}))
export default About9、自定义hooks
Section titled “9、自定义hooks”自定义
Hook本质上只是一种函数代码逻辑的抽取,严格意义上来说,它本身并不算React的特性。
- vue 中的 hooks 的定义没有要求
- 通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。
- react 中的 hooks 的定义函数的名字,必须要以
use开口
1、获取windows 的滚动位置
Section titled “1、获取windows 的滚动位置”原理,每次滚动的时候都会 进行 setState(),
因此每次滚动都会重新渲染当前组件,
也就是每次都会执行
useFetchXy(), 返回新的x,y,
- 渲染频率过高的情况下,将不必要的重新计算的逻辑数据进行记忆
import { useEffect, useState } from "react"
export function useFetchXy() { let [x, setX] = useState(window.scrollX) let [y, setY] = useState(window.scrollY)
useEffect(() => { const foo = e => { setX(window.scrollX) setY(window.scrollY) }
window.addEventListener("scroll", foo) return () => { window.removeEventListener("scroll", foo) } }, [])
return [x, y]}2、封装localStorage
Section titled “2、封装localStorage”import { useEffect } from "react"import { useState } from "react"
function useLocalStorage(key) { // 1.从localStorage中获取数据, 并且数据数据创建组件的state const [data, setData] = useState(() => { const item = localStorage.getItem(key) if (!item) return "没有对应的key" return JSON.parse(item) })
// 2.监听data改变, 一旦发生改变就存储data最新值 useEffect(() => { localStorage.setItem(key, JSON.stringify(data)) }, [data])
// 3.将data/setData的操作返回给组件, 让组件可以使用和修改值 return [data, setData]}
export default useLocalStorage第三节、redux hooks
Section titled “第三节、redux hooks”在使用类组件开发,使用
react-redux库、中的Provide和connect来和react 类组件进行结合
-
但是这种方式必须使用高阶函数结合返回的高阶组件;
-
并且必须编写:
mapStateToProps和mapDispatchToProps映射的函数; -
在
react-Redux7.1开始(应该是19年发布的),提供了Hook的方式,不需要在编写connect以及对应的映射函数了
useSelector
Section titled “useSelector”useSelector的作用是将state映射到组件中,代替了
mapStateToProps
-
参数一:<
function类型>( 会将state赋值给形参),通过返回值 将state映射到需要的数据中; -
参数二:
shallowEqual函数,可以进行比较来决定是否组件重新渲染。redux 每次修改 store 的值,都会重新创建一个
state对象,因此如果父子组件都有使用redux中的数据,父组件修改了 state 中的值,导致重新创建 state,这样的话,子组件没有修改也会刷新,造成性能浪费-
重点知识点注意:参数二比较的是,将前一个,
return的对象,和当前return的对象进行 浅层比较,如果相同的就不进行更新 -
注意:是返回的对象比较而不是
state的前后对象的比较 -
作用到子组件上
Section titled “作用到子组件上”
-
-
返回值:参数一根据
state返回的值
import React, { memo } from 'react'import { forwardRef } from 'react'import { shallowEqual, useSelector } from 'react-redux'
const About = memo(forwardRef((props, ref) => { console.log("开始渲染") const { message } = useSelector(state => ({ message: state.counter.get("message") //添加第二个参数 }), shallowEqual) return ( <div> <h2>About</h2> <div className="box"> {message} </div> </div> )}))
export default AboutuseDispatch
Section titled “useDispatch”
useDispatch使用方法很简单没有参数,直接调用,useDispatch的返回值就是dispatch。
- 每次更新都会
reducer都会返回一个新的state
import React, { memo } from 'react' import { shallowEqual, useDispatch } from 'react-redux' import {addCounter} from '../store/counterStore'
const dispatch = useDispatch() function trigger() { dispatch(addCounter(100)) }第四节、react 18 新增hooks
Section titled “第四节、react 18 新增hooks”1、useId
Section titled “1、useId”Server side render, 指的是页面在服务器端已经生成了完成的
HTML页面结构,不需要浏览器执行js代码创建页面结构
-
SSR:在接收到请求的时候,后台通过一些模板引擎,结合后台获取的数据信息对html进行组装,因此浏览器接收到的是完整的html页面- 这样爬虫发起请求的时候也可以获取完整的
html,之后进行解析获取
- 这样爬虫发起请求的时候也可以获取完整的
-
CSR: 页面结构会由浏览器执行js进行页面结构的搭建,所有的数据都是由ajax进行请求,之后展示到浏览器上,因此浏览器需要先下载js文件,之后在做执行
这个概念是在前端三大框架,出现后产生的,最早应该是
Angular中出现的
-
同构是一种SSR的形态,是现代SSR的一种表现形式。
-
当接收到网络请求之后,服务器渲染出首页的内容,会返回html 字符串。
但仅 HTML 不足以使页面具有交互性。例如,浏览器端 JavaScript 为零的页面不能是交互式的
-
为了使我们的页面具有交互性,除了在 Node.js 中将页面呈现为 HTML 之外,UI 框架(Vue/React/…)还在会浏览器中加载。这个过程称为
hydration -
但是对应的的代码同样可以在客户端执行
-
执行的目的包括事件绑定等以及其他页面切换时也可以在客户端被渲染;
-
useId的作用
Section titled “useId的作用”
useId是一个用于生成横跨服务端和客户端的稳定的唯一 ID 的同时避免 hydration 不匹配的 hook。
-
useId是用于react的同构应用开发的,前端的SPA页面并不需要使用它 -
useId可以保证应用程序在客户端和服务器端生成唯一的ID,这样可以有效的避免通过一些手段生成的id不一致,造成
hydration mismatch(就是一种错误);
2、useTransition
Section titled “2、useTransition”官方解释:会返回一个状态值表示过渡任务的等待状态,以及一个启动该过渡任务的函数。
-
当一些业务逻辑复杂的同步任务在执行时会造成
js执行时出现卡顿,用户体验很差,这个时候,可以利用这个过渡任务,让该部分任务优先级调低,当其他逻辑,或者数据,先执行完或渲染完之后在进行处理 -
注意:首次输入会有优化,但是
pending为ture等待的时侯执行其他操作还是有卡顿渲染优先级降低
-
faker使用方法看github
npm install --save-dev @faker-js/faker使用 useTransition
Section titled “使用 useTransition”-
标准
hooks返回值Array类型pending:boolean过渡任务的等待状态startTransition:function类型,启动过渡任务的函数- 参数:也是函数类型,包裹过渡任务
import React, { memo, useState, useTransition } from 'react'import namesArray from './namesArray'
const App = memo(() => { const [showNames, setShowNames] = useState(namesArray) const [ pending, startTransition ] = useTransition()
function valueChangeHandle(event) { startTransition(() => { const keyword = event.target.value const filterShowNames = namesArray.filter(item => item.includes(keyword)) setShowNames(filterShowNames) }) }
return ( <div> <input type="text" onInput={valueChangeHandle}/> <h2>用户名列表: {pending && <span>data loading</span>} </h2> <ul> { showNames.map((item, index) => { return <li key={index}>{item}</li> }) } </ul> </div> )})
export default App3、useDeferredValue
Section titled “3、useDeferredValue”延时的意思,和
useTransition作用相似,对于该 hooks 返回副本的更新也会降低渲染优先级
- 官方解释:
useDeferredValue接受一个值,并返回该值的新副本,该副本将推迟到更紧急地更新之后。 - 个人理解:和
toTransition作用相同,就是少了个pending
import React, { memo , useDeferredValue, useState} from 'react'import {arr} from '../utils/faker/fakerCitys'
export const Home = memo(() => { const [val, setVal] = useState("") let [names, setNames] = useState(arr)
// deferNames 这个副本的渲染的优先级会降低 let deferNames = useDeferredValue(names)
function trigger(e) { setVal(e.currentTarget.value)
deferNames = arr.filter(res => { return res.props.children .toLowerCase() .includes(e.currentTarget.value.toLowerCase()) })
setNames(deferNames) }
return ( <div> <h3>用户信息</h3> <div className="container"> <div className="inp"> <input type="text" onChange={trigger} value={val} /> </div> <div className="content"> {/*deferNames 的渲染优先级会降低,会等待其他元素先渲染*/} {deferNames} </div> </div> </div> )})
export default Home5、常用的hook
Section titled “5、常用的hook”- 中英文时间格式
import dayjs from 'dayjs';import { useLocale } from 'next-intl';
const useLocaleDate = () => { const local = useLocale();
const dateFormat = ({ date, lang = local, zhFormat = 'YYYY年M月D日', enFormat = 'MMMM D, YYYY', }: { date: string; lang?: string; zhFormat?: string; enFormat?: string; }) => { return ['zh-tw', 'zh-cn'].includes(lang) ? dayjs(date).locale('zh-cn').format(zhFormat) : dayjs(date).locale('en').format(enFormat); };
const dateRender = ({ enDate, otherDate }: { enDate: string; otherDate: string }) => { if (['en'].includes(local)) { return enDate; }
return otherDate; };
return { dateFormat, dateRender, };};
export default useLocaleDate;单页面富应用的2个问题
首屏的渲染速度
- 请求一个index.html 文件
seo优化的问题
第五节、react 面试问题
Section titled “第五节、react 面试问题”5.1、react 高阶组件与hooks 的区别和各自的用途
Section titled “5.1、react 高阶组件与hooks 的区别和各自的用途”高阶组件增强和复用现有的组件
Hooks 更多抽取通用逻辑,或者单独的功能,服务于组件