前言

eg:代表代码对照 若文章有误,欢迎读者留言反馈

React Hooks

函数式编程变成越来越流行,函数组件通过hooks也能像类组件保存数据状态,使用类似生命周期函数,

Hooks【钩子,把功能拿过来用】简介:
React的世界中,有容器组件和UI组件之分,在React Hooks出现之前,UI组件我们可以使用函数,无状态组件来展示UI,而对于容器组件,函数组件就显得无能为力,我们依赖于类组件来获取数据,处理数据,并向下传递参数给UI组件进行渲染。在我看来,使用React Hooks相比于从前的类组件有以下几点好处:

  1. 代码可读性更强,原本同一块功能的代码逻辑被拆分在了不同的生命周期函数中,容易使开发者不利于维护和迭代,通过React Hooks可以将功能代码聚合,方便阅读维护
  2. 组件树层级变浅,在原本的代码中,我们经常使用HOC/render props等方式来复用组件的状态,增强功能等,无疑增加了组件树层数及渲染,而在React Hooks中,这些功能都可以通过强大的自定义的Hooks来实现

useState保存数据状态【实习了类似state】

函数组件不能放数据状态,没有生命周期,没有实例,没有this,使用useState这个钩子函数

对比之前类组件保存组件状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React, { Component } from 'react'

class ClassComponent extends Component {
state = {
arr: [1,2,3,4]
}
render() {
return (
<div>
<h2>类组件与函数组件使用hooks对比</h2>
<p>类组件遍历</p>
<ul>
{
this.state.arr.map((item, index) => {
return <li key={index}>{item}</li>
})
}
</ul>
</div>
)
}
}

export default ClassComponent

通过传入 useState 参数后返回一个带有默认状态和改变状态函数的数组。通过传入新状态给函数来改变原本的状态值。**值得注意的是 useState 不帮助你处理状态,相较于 setState 非覆盖式更新状态,useState 覆盖式更新状态,需要开发者自己处理逻辑。

函数组件使用useState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 引入useState这个钩子
import React, { useState } from 'react'
// useState 赋予我们的函数组件具有使用数据状态的功能
const ClassComponent = () => {
// arr是数据状态,状态名自定义,useState参数就是变量的初始值
const [arr] = useState([1, 2, 3, 4]) // 通过useState这个hooks来赋予函数组件可以放数据状态的功能。
return (
<div>
<h2>类组件与函数组件使用hooks对比</h2>
<p>函数组件遍历</p>
<ul>
{
arr.map((item, index) => {
return <li key={index}>{item}</li>
})
}
</ul>
</div>
)
}

export default ClassComponent

useState工作中常用语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 引入useState这个钩子
import React, { useState } from 'react'

const UseStateDemo = () => {
// num是数据状态 setNum用来修改数据状态 useState参数就是变量的初始值
// setNum是用来更新前面的变量的值,你需要传入的新的数据,也需要遵循不可变值。可以做scu性能优化
// 数字类型
const [num, setNum] = useState(0)
// 布尔值
const [bool, setBool] = useState(false)
// 字符串类型
const [str, setStr] = useState('hello')
// 数组
const [arr, setArr] = useState([1,2,3,4])
// 对象
const [obj, setObj] = useState({name: '小明', age: 18})
return (
<div>
<h3>useState基本使用</h3>
{/* 数字类型 */}
<p>num值:{num}</p>
{/* 传参和类组件一样也需要回调 */}
<button onClick={() => setNum(num + 1)}>点击num加1</button>
{/* 布尔值 */}
<p>bool值:{JSON.stringify(bool)}</p>
<button onClick={() => setBool(!bool)}>点击取反</button>
{/* 字符串类型 */}
<p>str值:{str}</p>
<button onClick={() => setStr(str+'world')}>点击拼接world</button>
{/* 数组 */}
<p>arr值:{arr}</p>
<button onClick={() => setArr([...arr, 5])}>点击往arr里添加元素</button>
{/* 对象 */}
<p>对象值:{JSON.stringify(obj)}</p>
{/* 使用...对象覆盖,遵循不可变值, */}
<button onClick={() => setObj({...obj, name: '小红'})}>点击修改对象里的name</button>
</div>
)
}

export default UseStateDemo

似乎有了useState后,函数组件也可以拥有自己的状态了,但仅仅是这样完全不够。

useEffect处理副作用【实现了类似监听和生命周期】

useEffect处理副作用,在组件渲染节点的同时可以做一些我们自己的逻辑,我们就叫副作用。

  1. useEffect 用法1

说明:类似于watch监听,但是useEffect首次会执行一次里面回调函数,而watch则不会,可以设置immediate:true就能首次监听了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 引入useState、useEffect两个hooks
import React, { useState, useEffect } from 'react'

// useEffect 处理副作用,在组件渲染节点的同时可以做一些我们自己的逻辑,我们就叫副作用。
const UseEffectDemo = () => {
const [num, setNum] = useState(0)
const [str, setStr] = useState('aaa')
// useEffect 用法1 类似于watch监听,但是useEffect首次会执行一次里面回调函数,而watch则不会,可以设置immediate:true就能首次监听了
// 参数1,回调函数,在参数数组中依赖的变量发生改变的时候就执行。
// 参数2. 数组中放我们的依赖项,可以放多个值,有任意一个改变,参数1回调执行。
useEffect(() => {
console.log('我被调用了') // 初始执行一次,然后每次点击都会执行
}, [num, str])
return (
<div>
<h3>useEffect处理副作用</h3>
<p>{num}</p>
<button onClick={() => setNum(num + 1)}>点击+1</button>
</div>
)
}

export default UseEffectDemo
  1. useEffect 用法2

说明:参数2是空数组情况下,可以当做生命周期componentDidMount 挂载后来使用,挂载后会自执行一次,由于没有依赖项,也就不会再执行里面回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 引入useState、useEffect两个hooks
import React, { useState, useEffect } from 'react'

// useEffect 处理副作用,在组件渲染节点的同时可以做一些我们自己的逻辑,我们就叫副作用。
const UseEffectDemo = () => {
const [num, setNum] = useState(0)
const [str, setStr] = useState('aaa')
// useEffect参数1,回调函数,在参数数组中依赖的变量发生改变的时候就执行
// useEffect 用法2 参数2是空数组情况下,可以当做生命周期componentDidMount 挂载后来使用,挂载后会自执行一次,由于没有依赖项,也就不会再执行里面回调
useEffect(() => {
console.log('我被调用了') // 只会在初始挂载后执行一次,之后点击不再执行
}, [])
return (
<div>
<h3>useEffect处理副作用</h3>
<p>{num}</p>
<button onClick={() => setNum(num + 1)}>点击+1</button>
</div>
)
}

export default UseEffectDemo
  1. useEffect 用法3

说明:空着不写参数2的情况下,组件更新就会调用一次,当做componentDidUpdate更新后生命周期来使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 引入useState、useEffect两个hooks
import React, { useState, useEffect } from 'react'

// useEffect 处理副作用,在组件渲染节点的同时可以做一些我们自己的逻辑,我们就叫副作用。
const UseEffectDemo = () => {
const [num, setNum] = useState(0)
const [str, setStr] = useState('aaa')
// useEffect参数1,回调函数,在参数数组中依赖的变量发生改变的时候就执行
// useEffect 用法3 参数2 空着不写参数2的情况下,组件更新就会调用一次,当做componentDidUpdate更新后生命周期来使用
useEffect(() => {
console.log('被调用了') // 初始执行一次,每次点击数据更新都会执行
})
return (
<div>
<h3>useEffect处理副作用</h3>
<p>{num}</p>
<button onClick={() => setNum(num + 1)}>点击+1</button>
</div>
)
}

export default UseEffectDemo
  1. useEffect 用法4

说明:参数1 回调中的return后面的函数中可以做清除计时器,dom事件,自定义事件等清理工作

1
2
3
4
5
6
7
8
useEffect(() => {
// 计时器 自定事件 dom事件 需要手动清除
// 在return个回调,回调里面做清除计时器,dom事件,自定义事件等清理工作
return () => {
// useEffect 参数1 回调中的return后面的函数中可以做清除计时器,dom事件,自定义事件等清理工作
// 相当于react生命周期中的 componentWillUnmount 销毁前
}
}, [num])

useContext【实现了类似跨层级通信】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 引入createContext、useState、useContext三个hooks
import React, { createContext, useState, useContext } from 'react'

// 创建一个全局的context【不要把类组件的全局context和函数组件的全局context变量名设置为相同,会冲突,项目中会抽离到一个context.js文件中,这时数据源和接收数据的组件都要引入context】
var context = createContext()

// 父组件
const ContextDemo = () => {
const [arr, setArr] = useState([1,2,3,4])
return (
<div>
<h3>父组件</h3>
// 父组件向后提供数据需要使用context.Provider,设置value属性往后传递数据即可
<context.Provider value={arr}>
<Child1 />
</context.Provider>
</div>
)
}

export default ContextDemo

// 儿子组件
function Child1() {
return (
<div>
<h3>儿子组件</h3>
<Child2 />
</div>
)
}

// 孙子组件
function Child2() {
// useContext参数是定义好的全局Context,直接调用就可以获取到祖先组件传递的值
var arr = useContext(context)
return (
<div>
<h3>孙子组件</h3>
<p>{arr}</p>
</div>
)
}

useReducer【实现了类似Redux/React-Redux】

useReducer这个Hooks在使用上几乎跟Redux/React-Redux一模一样,唯一缺少的就是无法使用redux提供的中间件,redux可以通过中间件来增加的,redux-thunk中间件可以书写异步。useReducerredux的简化版,不支持异步

用法跟Redux基本上是一致的,用法也很简单,算是提供一个miniRedux版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 引入useReducer这个hooks
import React, { useReducer } from 'react'

// redux初始值【与store里的defaultState一样】
const initState = {
num: 0
}
// 书写逻辑reducer
const reducer = (state, actions) => {
// 这里也是可以替换为switch的,actions.type规则,通过对象dispatch触发规则,满足就执行这个
if (actions.type === 'add') {
// actions.val拿到传递的值
return { num: state.num + actions.val }
} else if (actions.type === 'sub') {
return { num: state.num - actions.val }
} else {
return new Error('没有这个操作')
}
}


const UseReducerDemo = () => {
// 使用useReducer,参数1,放入一个reducer函数,同样这个reducer也是state的修改逻辑
// 参数2,initState就是state的默认值、初始值。
const [state, dispatch] = useReducer(reducer, initState)
return (
<div>
<h3>简化版redux用法</h3>
<p>{state.num}</p>
{ /* dispatch派送一个对象,里面有规则和传递数据 */ }
<button onClick={() => dispatch({type: 'add', val: 1})}>点击+1</button>
<button onClick={() => dispatch({type: 'sub', val: 1})}>点击-1</button>
</div>
)
}

export default UseReducerDemo