1.关于 React Hooks
React 提倡函数式编程,view = fn(props),函数更灵活,更易拆分,更易测试。尽管函数组件有着许多优势,但是函数组件太简单了,许多功能class组件能轻易实现的功能函数组件很难或者不可能实现。因次,为了让函数组件拥有更强大的能力,Hooks出现了。
关于 Hooks 介绍,无论是官方文档还是网络上都已经有了很多介绍,下面直接对常用的一些 hook 使用进行介绍。
2. State Hook
函数组件是一个纯函数,执行完就销毁了,无法存储state,如果想拥有像 class 组件中的state功能,这时就需要 state hook,把 state 功能 “钩”到纯函数中
2.1 函数组件基本“格式”
import React from 'react'
const HooksT = () => {
return (
<>
这是一个函数组件
>
)
}
export default HooksT;
2.2 使用useState
下面是在函数组件中实现加一减一效果,在class组件中可以通过state和setState来实现(注意,这里的state和setState是class组件中固定的唯一写法,和函数组件中的不同)
import React, { useState } from 'react'
const HooksT = () => {
/**
* 数组解构,首先要引入:import { useState } from 'react'
* 1.[count, setCount],这个可以自定义名字,但是一般来说,都是以[值,set值]这种命名方式
* 2.useState(0)中的0是值的初始值,也可以是是字符串,数组,对象等
* 3.如果值为对象,可以这样写:useState({a:'1',b:'2'})
* 4.setCount这是可以对count进行修改的方法,如设置count值为5,则:setCount(5);设置count值加一,则:setCount(count + 1)
* 5.这样的useState可以在函数组件中没有数量限制
*/
const [count, setCount] = useState(0)
return (
<>
这是一个函数组件
{`num:${count}`}
>
)
}
export default HooksT;
3. useEffect
函数组件是一个纯函数,执行完即销毁,自己无法实现生命周期,可以用 useEffect 把生命周期 “钩” 到纯函数中,用 useEffect 模拟组件生命周期
3.1 使用 useEffect
import React, { useState, useEffect } from 'react'
const HooksT = () => {
const [count, setCount] = useState(0)
/**
* 1.模拟class组件的 DidMound 和 DidUpdate 生命周期
* 当界面第一次渲染完成,或者更新时重新渲染后都会执行
*/
useEffect(() => {
console.log('当界面第一次渲染完成,或者更新时重新渲染后都会执行')
})
/**
* 2.仅模拟class组件的 DidMound 生命周期(在useEffect的第二个参数加一个空数组)
* 当界面第一次渲染完成执行,更新时不执行
*/
useEffect(() => {
console.log('当界面第一次渲染完成执行,更新时不执行')
}, [])
/**
* 3.仅模拟class组件的 DidUpdate 生命周期(在useEffect的第二个参数加一个数组,数组里放入需要监听更新的值)
* 当界面第一次渲染完成执行,更新时不执行
* 注意1:这里useEffect中第二个参数其实是一种依赖关系,和第一点中的不同,第一点在useEffect中不加第二个参数时
* 无论什么数据更新都会执行; 而此处增加 [count] 后,仅仅当count更新时才会执行,除此之外的更新不会执行。
* 注意2:数组中可以放多个依赖的值,其中任何一个更新都会执行
*/
useEffect(() => {
console.log('当界面第一次渲染完成执行,更新时不执行')
}, [count])
/**
* 4.模拟class组件的 WillUnMount ,在销毁时执行,这一步谨慎使用
* 在定义一些定时任务和全局变量时,一定要返回一个函数去进行销毁,否则会
* 造成内存泄漏
*/
useEffect(() => {
console.log('当界面销毁时执行')
let timerId = window.setInterval(() => {
console.log(timerId)
})
// 返回一个函数,模拟一个函数
// 当不return时,定时任务其实不会结束,为了完全销毁,需要增加return,并返回一个函数
return () => {
window.clearInterval(timerId)
}
}, [])
return (
<>
这是一个函数组件
{`num:${count}`}
>
)
}
export default HooksT;
3.2 useEffect 使用总结
1.同时模拟 componentDidMount 和 componentDidUpdate --- useEffect无依赖
2.模拟 componentDidMount - useEffect 依赖 []
3.模拟 componentDidUpdate - useEffect依赖[a, b]
4.模拟 componentDidWillUnMount - useEffect 中返回一个函数,依赖 []
3.3 useEffect 让纯函数有了副作用
1.默认情况下,执行纯函数,输入参数,返回结果,无副作用
2.所谓副作用,就是对函数之外造成了影响,如设置全局定时任务
3.而某些时候需要副作用,则可以用 useEffect “钩” 到纯函数
3.4使用 useEffect 模拟 WillUnMount 时的注意事项
注意,模拟 WillUnMount ,但不完全相等
useEffect 中返回函数 fn:
1.useEffect 依赖 [],组件销毁时执行fn,等于 WillUnMount,
2.useEffect 无依赖或者依赖 [a, b] ,组件更新时会执行 fn,
4. 其他 Hooks
针对其他一些不常用到的 hooks 进行简单介绍:
1.useRef
2.useContext
3.useReducer
4.useMemo
5.useCallback
4.1 useRef
1.useRef常用于获取 DOM 节点
import React, { useEffect, useRef } from 'react'
const HooksT = () => {
const btnRef = useRef(null)
useEffect(() => {
console.log(btnRef.current) // btnRef所绑定的 DOM 节点
}, [])
return (
<>
这是一个函数组件
>
)
}
export default HooksT;
2.可用于保存一些其他数据
// 定义一个ref
const ref = useRef()
// 可以对ref进行一个赋值操作
ref.current = 100;
4.2 useContext
提供了更加高级的一种组件中传递值的方式,不再需要一层一层的向下传递,而是可以隔层传递。
import React, { useContext } from 'react'
// 主题颜色
const themes = {
light: {
foreground: '#000',
background: '#eee',
},
dark: {
foreground: '#fff',
background: '#222',
},
}
// 创建 Context
const ThemeContext = React.createContext(themes.light)
/**
* 下面定义三个组件演示隔层传递
*/
const ThemeButton = () => {
const theme = useContext(ThemeContext)
return (
)
}
const Toolbar = () => {
return (
<>
>
)
}
const HooksT = () => {
return (
<>
{/* ThemeContext.Provider */}
>
)
}
export default HooksT;
我们可以不再一层一层的去传递某些值,而是可以统一的在最顶层组件传入,然后可以在下面任何一个组件内传递,从而减少了多层组件传递的麻烦,只需要在顶层做一次修改,就能修改整个大组件,例如做主题管理。而无需再引用 Redux
4.3 useReducer
useRedecer 和 useState很像,当我们想要实现更加复杂的修改值的操作,可以使用 reducer
import React, { useReducer } from 'react'
// 定义初始值
const initialSatate = {
count: 0,
}
// 定义修改规则
const reducer = (state, action) => {
switch(action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
return false;
}
}
const HooksT = () => {
// 创建一个 reducer ,很像useState
const [state, dispatch] = useReducer(reducer, initialSatate)
return (
<>
count: {state.count}
>
)
}
export default HooksT;
useRedecer 和 redux 区别:
1.useReducer 是 useState 的代替方案,用于 state 的复杂变化
2.useReducer 是单个组件的状态管理,组件间通讯还是需要 props
3.redux 是全局的状态管理,多组件共享数据
因此,useReducer 本质上只是 useState 的升级版,并不能代替 redux
4.4 useMemo
在react中,默认情况下当父组件重新渲染时,子组件也会无条件重新渲染,当遇到性能瓶颈时,可以考虑使用 useMemo 做性能优化
1.首先要使用memo将子组件封装起来
import React, { memo } from 'react
// 通过memo将子组件封装起来,即作为参数传入memo()中
const Child = memo((info) => {
console.log("子组件渲染了")
return (
子组件
)
})
2.在父组件中使用 useMemo 对传入子组件的数据进行一个依赖判断
import React, { useMemo } from 'react
import Child from 'path'
const Parent = () => {
// 通过 useMemo 来封装需要传入子组件的数据,即进行缓存数据
// 数据通过return来返回
cosnt info = useMemo(() = {
return {
name: 'jack',
age: 18
}
},[name]) // 依赖name,只有name变化时子组件才会重新渲染
console.log("父组件渲染了")
return (
<>
父组件
>
)
3. useMemo 使用总结
1.React 默认在父组件更新情况下会无条件更新子组件
2.class 组件使用SCU和PureComponent 做优化
3.Hooks 中使用 useMemo做优化
4.上面2,3点的优化原理是一样的,都是通过对 props 的一个浅层对比进行优化
4.5 useCallback
useCallback 是对 useMemo 的一个补充,针对父组件传入函数时进行缓存
只需要在 4.4 中 Parent 组件中增加对函数的缓存即可:
import React, { memo, useMemo, useCallback } from 'react
import Child from 'path'
// 在这增加传入的 onChange 函数
const Child = memo(({info, onChange}) => {
console.log("子组件渲染了")
return (
子组件
)
})
const Parent = () => {
// 通过 useMemo 来封装需要传入子组件的数据
cosnt info = useMemo(() = {
return {
name: 'jack',
age: 18
}
},[name])
// 将要传入子组件的函数作为参数传入 useCallback 中即可
const onChange = useCallback(() => {
console.log("useCallback")
})
console.log("父组件渲染了")
return (
<>
父组件
>
)
}
5.自定义Hook
5.1 通过定义一个axios相关的hook来演示自定义hook
安装Axios: yarn add axios --save
import { useState, useEffect } from 'react';
import axios from 'axios';
/**
* 封装 axios 自定义hook发送请求
*/
const useAxios = (url) => {
const [loading, setLoading] = useState(false)
const [data, setData] = useState()
const [error, setError] = useState()
useEffect(() => {
// 表示开始请求
setLoading(true)
// 利用 axios 发送网络请求
axios.get(url)
.then(res => {
setData(res)
})
.catch(err => {
setError(err)
})
.finally(() => {
setLoading(false)
})
}, [url])
return [loading, data, error]
}
export default useAxios;
小结:
1.本质是一个函数,命名以 use 开头
2.内部能正常使用 useState, useEffect 或者其他 Hooks
3.自定义返回结果,格式不限
5.2 第三方Hooks:
https://ahooks.js.org/zh-CN/docs/getting-started
6.Hooks 使用规范
6.1 命名规范
自定义 hooks 使用 use 开头的命名,非 hooks 尽量避免使用 use 开头命名
6.2 Hooks使用规范
1.只能用于 React 函数组件和自定义 Hook 中,其他地方不可以
2.只能用于顶层代码,不能在循环、判断(if)中使用 Hooks
3.eslint 插件 eslint-plugin-react-hook 可以检测使用规范
6.3 关于Hooks的调用顺序
函数组件,本质是一个函数,执行完即销毁;
无论是组件初始化还是组件更新,都会重新去执行这个函数(表示从销毁到再一次初始化);这一点和class组件不同,因为class组件会有组件实例,只要组件不被主动销毁,组件更新是不会导致calss组件销毁。
1.对于 useState
/**
* render:初始化 state 的值
* re-render: 只恢复为第一次初始化的 state 的值,不会再重新设置新的值
* 只能通过 setState 修改
*/
const [state, useState] = useState("name")
之所以 hooks 能到正确获取到对应的值(多个state能一一对应),是因为 hooks 是严格按顺序读取的,如果有 hooks 在 if 内,当 if 不通过时,if 内的 hooks 就不会执行,也就会导致剩下的hooks 全部错乱。
2.对于 useEffect
/**
* render:添加 effect 函数
* re-render:替换 effect 函数,useEffect内部的匿名函数也会重新定义
*/
useEffect(() => {
console.log("useEffect")
})
同样,当在 if 中使用 useEffect 时,因为执行的不确定性,也会导致前后的 useEffect 执行错乱
hooks严格依赖于执行顺序,绝对不能在循环,判断中使用
7.React Hooks 组件逻辑复用
7.1 class组件的逻辑复用
1.Mixins 已经废弃
2.高阶组件HOC
3.Render Prop
7.2 使用 Hooks 做组件逻辑复用
1.定义一个公共的自定义hooks
2.直接在需要的地方使用即可
8.React Hooks 注意事项
1.useState 初始化值,只有第一次有效
import React, { memo, useMemo, useCallback } from 'react
import Child from 'path'
const Child = memo(({info}) => {
const [nameC, setNameC] = useState(info.name)
return (
子组件
传进来 props 的 name1:{info.name}
初始化 nameC 的name2:{nameC}
)
})
const Parent = () => {
const [name, setName] = useState("name")
const info = {
name
}
return (
<>
父组件
>
)
在上面的演示代码中,当第一次运行后:name1 和 name2 值都是 ”name“,而当点击按钮修改传入的info时,name1 变成了 ”nameChange“,而name2 仍然时 ”name“
由此可以印证,useState 只有在初次渲染时会给 nameC 初始值,而在后面即使改变传入的值,useState 也不会再重新设置新的值,只能通过 setNameC来修改
2.useEffect 内部不能修改 state 的情况
使用 useEffect 时,当依赖为 [] 时,re-render 是不会重新执行 effect 函数的,这样就会导致在 useEffect 中的一些 set值 的操作没有效果
当没有依赖,或者依赖 [a, b] 中会 a 或 b 会改变的情况时,re-render 会重新执行函数,这样状态又不能保存下来
这时可以通过useRef来保存一个状态值
解决方案:
// 可以使用 useRef
// 通过 useRef 为 countRef 赋值为 0
const countRef = useRef(0)
// 然后可以进行修改 countRef , countRef.current 就是它的值
++countRef.current
3.useEffect 可能出现死循环
当 useEffect 的依赖里面有 {} , [] 引用类型时,会导致死循环
这是因为,react 的 useEffect 判断依赖是否变化是通过 Object.is() 这个函数,而 Object.is({}, {}) 和 Object.is([], []) 都会返回false,则 useEffect 判断为依赖变化,就会执行 effect 函数,这样就会导致死循环的发生
后续
1.webpack的完整配置流程,帮助掌握基本配置和部分高级配置
2.JavaScript版数据结构与算法,着重于实战练习
3.dva的介绍与使用
小伙伴们有需要想让作者先整理出来的可以留言评论,我会参考大家的意见来选择