React 简介

一、什么是 React ?(What is React?)

React是一个声明式的,高效的,并且灵活的用于构建用户界面的 JavaScript 库。它允许您使用”components(组件)“(小巧而独立的代码片段)组合出各种复杂的UI。

二、React谁开发的?

由Facebook开发且开源,近十年“陈酿”,阿里等大厂开始使用

三、为什么要学?

 (一)原生js痛点

  1. 原生JavaScript操作DOM繁琐、效率低(DOM-API操作UI)
1
2
3
document.getElementById('app')
document.querySelector('#app')
document.getElementsByTagName('span')
  1. 使用JavaScript直接操作DOM,浏览器会进行大量的重绘重排

  2. 原生JavaScript没有组件化(模块化)编码方案,代码复用率低

(二)React特点

  1. 采用组件化模式,声明式编码,提高开发效率和组件复用率

  2. 在React Native中可以用React语法进行移动端开发

  3. 使用虚拟DOM和优秀的Diffing算法,尽量减少与真实DOM的交互,提高性能

四、React依赖介绍?

待更新~

React笔记

index.js 入口js: 用react-dom渲染注入点

1
2
3
const root = ReactDOM.createRoot(dom节点)
root.render(<App />)

Jsx是什么?

JSX是React.createElement的语法糖

jsx和React.createElement的互相转换

1
2
3
4
5
const a = React.createElement("ul", null,
React.createElement('li', {className: 'item'}, 1),
React.createElement('li', {className: 'item'}, 2),
React.createElement('li', {className: 'item'}, 3),
)
1
2
3
4
5
<ul>
<li className='item'>1</li>
<li className='item'>2</li>
<li className='item'>3</li>
</ul>

children位置的内容自动转义

1
<div dangerouslySetInnerHTML={{__html: "<b>hello</b> world"}}></div>

循环渲染

数组里的jsx元素要有key属性,key值是能代表数据的唯一的字符串,通常来说key应该是数组元素的id值,如果没有这种数据用索引作为唯一值。

数组渲染时key的意义:

  • 提高渲染效率
  • key用index的缺陷:不能有效的更新jsx组件,index不得已的时候使用

代码碎片/代码片段组件: React.Fragment

目的:作为数组的上级容器组件,但不渲染额外dom元素
简写方式:<>...</>

自定义组件

命名:首字母大写的驼峰结构
函数组件:一个函数返回jsx对象就叫做函数组件
函数组件的属性就是函数的第一个参数,是object类型

父传子:通过属性

React的事件

React html组件上的dom定义的事件,回调函数里的event对象兼容dom的event

  • 取得触发事件的元素:event.target
  • 取得触发事件的名字:event.name
  • 取消默认动作:event.preventDefault()
  • 取消冒泡:event.stopPropagation()

状态 State (相对于Props)

props是一个js的object
state也是一个js的object
状态就是一种内部的属性 (不是通过父组件传给子组件的,是组件自己维护的属性)
父组件的state 传给子组件,就是子组件的属性

钩子函数 Hook 16.8

提供钩子函数使函数组件有各种其他的功能。
使用钩子函数的两个重点:

  1. 钩子函数只能使用在函数组件或者自定义的钩子函数里。
  2. 钩子函数只能在函数体的最外层生命周期里调用,不能在if/循环里调用。

对数组类型state的修改

正确处理方式:
将修改的内容放入一个新的数组 (对象),将它设置为下一次的状态,(当前状态 === 新的状态 是 false,就会进入重新渲染)

添加:

1
2
setState([...原数组,新增数据])
setState([...numbers,Math.random()])

删除:

1
2
setState(原数组.filter(过滤条件的函数))
setState(numbers.filter((n,i)=>i!==index))

修改:

1
2
3
4
5
6
7
setState([...原数组.slice(0,index),修改位置的新值,...原数组.slice(index+1)])

setState([
...state.slice(0,index),
{...state[index],name:10},
...state.slice(index+1)
])

类组件和函数组件的区别

  • 类组件有状态,函数组件无状态。
  • 类组件有生命周期函数,函数组件无生命周期函数。
    函数组件不是用来替换类组件的。

对状态值为数组类型的更新

增加

1
2
3
state = {numbers: []}
// 正确做法
this.setState({numbers: [...this.state.numbers, 1]})

删除

1
2
state = {numbers: [9,8,7]}
this.setState({numbers: numbers.filter((n, index) => index !== 2)})

修改

1
2
3
4
state = {numbers: [9,8,7,4]}
this.setState(
{numbers: [...numbers.slice(0, 1), 80, ...numbers.slice(2)]}
)

useEffect 函数的定义:

没有返回值

参数有2个:

第一个参数是一个回调函数,这个回调函数执行的内容叫做”副作用”,回调函数的返回值可以是空或者一个无参数的函数,无参数的函数叫做”消除副作用”

执行副作用的过程:挂载+更新后
消除副作用的过程:上次更新后的消除过程+卸载

函数组件的生命周期:

挂载:render,执行副作用
更新:render,消除上次副作用,执行副作用
卸载:消除上次副作用

第二个参数是指副作用的依赖关系(depends),数组或者空

  • depends = 空,挂载和卸载一定会执行,每次render后会执行
  • depends = [],只会挂载和卸载的时候执行,更新过程里不会执行
  • depends = [props1,state1 …],挂载和卸载一定会执行,这次渲染的时候数组当中的变量和上次渲染的时候是不一样的,如果发生变化就会执行,没有发生变化就不会执行

类组件的生命周期

三个大的阶段:挂载,更新,卸载

挂载阶段

- constructor(props)
  - 适合做初始化state和其他变量
  - 不适合做发起异步网络请求,查询dom,其他副作用的过程,this.setState(constructor初始化state用直接赋值就可以了,不用setState)
- render() 渲染
- componentDidMount() 组件挂载完成

更新阶段 (0次或n次)

- shouldComponentUpdate (nextProps /* 下次属性 */, nextState/* 下次状态 */) 返回boolean值。
    含义:要不要更新component?React.Component上的shouldComponentUpdate默认返回true
- componentDidUpdate(previousProps/* 上次的属性 */, previousState/* 上次的状态 */) 组件更新完成,dom元素渲染生效后 (previous上一次)
  - 适合发起副作用;测量dom元素;允许执行this.setState,注意有条件的执行

this.forceUpdate() 强制刷新,略过shouldComponentUpdate

卸载阶段

- componentWillUnmount() 组件即将被卸载,组件一生中只会执行一次
  - 适合做清理componentDidMount里开启的副作用
  - 不适合发起副作用,或者this.setState

Ref

作用:用来获取渲染dom元素或者jsx元素,函数组件获取不到ref

使用场景

  • 管理焦点,媒体播放,文本选择
  • 尺寸的测量
  • 强制触发动画
  • 集成第三方 dom 库

React不能用dom获取元素,要使用ref

使用 Ref

### 函数组件里使用

useRef 钩子函数

1
2
3
4
5
6
7
8
9
10
import { useRef, useEffect } from 'react'

function MyComponent() {
const h = useRef() // {current: undefined}
const k = useRef()
useEffect(() => {
console.log(h)
}, [])
return <h1 ref={h}>hello</h1>
}
ref 属性不是 html 组件的属性,类似于 key 属性,属于 react 系统所需要的属性 useRef() 总返回同一个对象 `{current: null}` ### 类组件里使用 ref 对象用 React.createRef()获得
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React from 'react'

class MyComponent2 extends React.Component {
constructor(props) {
super(props)
this.input = React.createRef() // {current: undefined}
}

render() {
return (
<div>
<h1 ref={this.input}>你好</h1>
<button onClick={() => console.log(this.input)}>click</button>
</div>
)
}
}
### 回调函数方式 形式:`<组件 ref={(元素) => {v1 = 元素}}/>` 用途: - 通用,函数组件和类组件都可以用 - 可以跟精细的处理ref属性
1
2
3
4
5
6
7
8
9
10
11
const MyComponent3 = () => {
let ref = null
return (
<>
<ul>
<li ref={(el)=>{ref=el}}>hello</li>
</ul>
<button onClick={() => console.log(ref)}>console.log(ref)</button>
</>
)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const students = [
{id:1,name: 'Alice'},
{id:2,name: 'Bob'},
{id:3,name: 'Carrey'}
]
const MyComponent4 = () => {
// let ref = [useRef(), useRef(), useRef()]
let ref = []
return (
<>
<ul>
{students.map((s, index) => <li key={s.id} ref={el => {
ref[index] = el
}}>{s.name}</li>)}
</ul>
<button onClick={() => console.log(ref)}>console.log(ref)</button>
</>
)
}

路由

原理

新浏览器:history对象管理的api (history.pushState/replaceState)
window.onpopstate - history记录前后移动的时候会触发这个事件

创建路由环境

浏览器环境里的路由器:BrowserRouter, HashRouter
import {BrowserRouter as Router} from ‘react-router-dom’
所有的路由都需要在路由环境里

配置路由

路由设置组件:Route, Switch, Redirect,Link,NavLink

Route组件

location 和 Route 规则如果匹配的上,就会渲染这个路由

属性:

  • 和匹配有关的属性
    • path 路径配置:必须从根目录开始(path=’/about’, path=’/account/passwd’, 错误path=’contact’)
    • exact 精确匹配,默认exact=false
    • strict 严格匹配,默认exact=false,精确匹配的优先级比严格匹配高
    • sensitive 大小写匹配,默认sensitive=false,大小写匹配优先级比精确匹配高,sensitive > exact > strict
  • 和渲染有关的属性
    • render: 回调函数(匿名自定义组件),返回jsx对象,render只在路由匹配情况下运行回调函数
    • children
      • 回调函数,返回jsx对象, 不论路由是否匹配,函数都运行
      • jsx元素
    • component: 自定义组件(不是jsx元素)

Switch组件

作用:Switch组件的children是一组Route组件,按顺序,哪个路由组件先匹配到当前的location,哪个路由就渲染,其他的路由被忽略。

Redirect组件

用组件渲染的方式实现地址专向

  • 路由属性
    • exact 精确匹配
    • strict 严格匹配
    • sensitive 大小写匹配

Link组件

属性

  • to
    • 字符串: to=’/‘, to=’/blogs?limit=10#blog-1’
    • location object,比字符串形式能够多传递state属性(历史记录的state)
    • 函数:(location) => 返回新location对象,为了生成动态的地址
  • replace 开关属性,replace默认值false,决定点击链接时是不是替换replace历史记录
  • component用其他的组件渲染Link组件

NavLink继承Link组件的所有属性

特殊属性:

  • 路由上的匹配属性
    • exact 精确匹配
    • strict 严格匹配
    • sensitive 大小写匹配
  • 显示是否选中的属性
    • className
      • 除了字符串类型以为可以写回调函数(isActive/有没有匹配当前路径/)=>返回自己的class Name
      • style
        • (isActive)=>返回自己的style对象

上下文 Context

用途

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。

Context什么时候用?

  • 共享全局状态
  • 风格主题
  • 应用设置
  • 登录的用户信息
  • 用户设置
  • 多语言系统
  • 一系列共享的服务

如何使用Context

第一步:创建Context

1
const Context = React.createContext(默认值)

注意:默认值是在消费Context的时候,没有指定Context的上下文的时候才会有效。

第二步:提供Context容器

Context.Provider 组件内的子组件都属于这个上下文的环境。

1
2
3
<Context.Provider value={value}>
...
</Context.Provider>

第三步:消费Context的Value

接收Context的value

包含三种方式

  1. 设置class的static contextType属性
  • 只能在类组件中使用
  • 通过this.context访问对应context的值
  • 特点:只能消费一个Context
1
2
3
4
5
6
7
class Child extends React.Component {
static contextType = MyContext
...
method() {
// 操作this.context
}
}
  1. Context.Consumer 消费者标签
  • 在类组件和函数组件中都可以使用
  • Context.Consumer元素的children只能有一个,且是以context的值为参数的回调函数,该函数应该返回所渲染的JSX对象
  • 特点:
    • 通用性好;
    • 支持多个上下文同时消费的能力;
    • 只能在render函数中使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
class Child extends React.Component {
render() {
return(
<MyContext.Consumer>
{
value => {
return ...
}
}
</MyConText.Consumer>
}
}
}
  1. useContext(Context)
  • 只能在函数组件中使用
  • useContext(Context)返回该context的值
  • 特点:
    • 支持多个上下文同时消费的能力;
    • 使用灵活。
1
2
3
4
function Child() {
const context = useContext(MyContext)
return ...
}

渲染原理

渲染并不是已经在dom上生成了html结构。渲染就是生成jsx对象(树)。

  • 挂载的渲染一定会发生
  • 重新渲染
    • 以下发生改变的时候渲染(更新时的渲染)
      • props
      • state
      • 消费的context
    • 类组件执行this.forceUpdate()
    • 父组件重新渲染的时候,所有后代节点都会重新渲染

类组件优化

通过继承React.PureComponent,可以优化组件,在自己的属性或state没有发生改变时,不会因为父组件重新渲染,而导致自身重新渲染。

1
2
3
class MyComponent extends React.PureComponent {
...
}

原理:

普通的时候类组件继承自React.Component,shouldComponentUpdate函数永远都直接返回true(不做对比),每次都渲染。

如果使用React.PureComponent作为父类,shouldComponentUpdate函数里会根据props/state判断和下次的props/state是否发生改变,如果有改变才返回true,否则返回false,返回false的时候不会render。

函数组件优化

通过React.memo返回组件的高阶组件,该高阶组件在外部的props没有发生变化时不会重新渲染。

1
2
3
4
let MyComponent = () => ...
MyComponent = React.memo(MyComponent)

export {MyComponent}

React.PureComponent 与 React.memo 功能对比
React.memo 返回值为高阶组件。它实现的效果与 React.PureComponent 相似,不同的是:

  • React.memo 用于函数组件
  • React.PureComponent 适用于 class 组件
  • React.PureComponent 只是浅比较 props、state,React.memo 也是浅比较,但它可以自定义比较函数

为什么不是所有的组件都按照PureComponent的机制当成默认渲染逻辑?

因为比较属性或state也有性能消耗,有些时候有些组件并不需要特别比较属性或state,因为他们本来就是每时每刻都会渲染的,因此这种时候做这种对比是没有意义的,反而增加了运算负担。

useCallback/useMemo

为什么要缓存function或对象?

  • 节省服务对复杂运算的损耗;
  • 从缓存机制的考虑:当组件上的属性值是引用类型(往往是传递回调函数给属性),父组件重新渲染的时候,子组件的这个属性总是得到一个新的对象,使上面的这种对比的机制失效,每次对比完的结果都是需要重新渲染,使得上面(PureComponent或React.memo)优化没有作用,反而做了多余的运算。

解决办法:让这种属性在没有发生功能的变化的时候使用相同的值

useCallback的用法

  • 参数:
    1. 函数
    2. 依赖关系数组:当依赖关系里的数据发生改变,会返回新的函数,如果依赖关系没变,得到上一次缓存的函数。
    • 注意:useEffect使用undefined作为依赖关系,有意义;useCallback使用undefined/null作为依赖关系,没有意义。
  • 返回值:函数

目的:组件属性得到的函数值,在不需要发生变化的时候,总得到同一个值,这样避免因为函数引用不同产生的多余的渲染

1
2
3
4
// 平时
const handleClick= (item, index)=> { ... }
// 优化
const handleClick = useCallback((item, index)=> { ... }, [...])

什么情况下使用useCallback

需要调用useCallback:
当传递属性的组件是PureComponent或者React.memo()的组件,回调方法需要用useCallback生成。

不需要调用useCallback:

  • 组件结构简单,重新渲染的效能不会消耗过高,不用做缓存。
  • html组件上的回调方法不用useCallback处理。

useMemo

  • 参数:
    1. 回调函数: 没有参数,有返回值
    2. 依赖关系的数组
    • 注意:useMemo的依赖关系是null/undefined,是没有意义的,因为这样每次render的时候,都会执行回调函数。
  • 返回值
    • 参数一:回调函数的执行结果

用途:

  1. 减少重复性的复杂运算
  2. 在依赖关系不变的情况下,得到相同的对象引用(跟useCallback得到不变的函数的理由类似)
1
2
3
4
const r = useMemo(() => {
console.log('useMemo 回调方法')
return 99*99
}, [])

useMemo和useCallback的差别

  • useMemo返回的值:任何类型的。useCallback返回函数。
    useCallback是useMemo的特殊情况:
    1
    2
    3
    4
    const fn = useCallback((e) => { ... }, [])
    const fn2 = useMemo(() => {
    return (e) => { ... }
    }, []) // 和useCallback效果一样
  • useMemo的回调函数会直接执行,useCallback的回调函数不会直接执行(“直接”指调用useMemo或useCallback的时候)
  • useMemo的回调函数没有参数,useCallback的回调函数可有参数

useReducer

作用:使用useReducer 替换 useState管理组件的状态。

reducer的概念来源

reducer 名字(概念)是来源于 Array.prototype.reduce函数。

1
2
3
4
5
6
7
8
9
10
Array.prototype.reduce((上一次运算的值, item, index, arraySelf) => {
... 用上一次的值和item等数据运算
return 下一次的值
}, 初始值)

Array.prototype.reduce(reducer, 初始值)

[5,6,7].reduce((sum, n) => {
return sum + n
}, 0) // 18

什么是reducer

reducer是这样一类函数:

  • 输入参数state和action,返回一个新的state
  • reducer是纯函数
1
2
3
4
const reducer = (state, action) => {
...根据state和action计算出下一次的state
return 下一次的state
}

纯函数

  • 不能运行副作用,比如网络请求、数据库查询
  • 纯函数返回值应该完全依赖于它接收的参数,用相同的参数调用纯函数,每次得到的结果必定相同
  • 纯函数的返回值不能有比如Math.random()或者Date.now()之类非纯函数参与
  • 不要试图修改纯函数的参数,不要改变全局环境里的变量

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const reducer = (state, action) => {
switch(action.type) {
case ...:
return 新的state
default:
return state
}
}
const initState = ...
const MyComponent = () => {
const [state, dispatch/*分发*/] = useReducer(
reducer, initialState/*初始状态*/)
return <><button
onClick={() => dispatch(action)}>...{state...}</button>
}

完整的函数形式

1
2
3
4
5
6
7
8
useReducer(reducer, initState, [initiator])

function initCount(count) { // 惰性初始化
return {count}
}

const [state/*当前状态,渲染的状态*/, dispatch/* 函数,发起动作 */]
= useReducer(reducer, 1, initCount) // 初始值 {count: 1}

区分什么情况下用useState 和 useReducer

  • useState:会影响state发生改变的条件是单一的。
  • useReducer:
    • 对一种数据操作有很多种不同的动作;
    • 一个动作对多种数据都有影响;
    • 状态结构比较复杂的时候用。

Redux

什么是Redux?

  1. 状态(State)的容器
  2. 状态是可以预测的。

什么是状态管理

  • 状态存储
  • 状态读取
  • 状态变更

什么是数据的不可变性 Immutability (Immu tability )

Immutable Data 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。

对象修改

1
2
3
const todo = {id:1, text:'打球', completed: false}
todo.completed = !todo.completed // 不符合 im
const newTodo = {...todo, completed: !todo.completed} // 符合

对象删除属性

1
2
3
const person = {name:'张三', age: 19}
const newPerson = {...person}
delete newPerson.age

数组添加数据

1
2
3
const a = [1,2,3]
a.push(4) // 不符合im
const b = [...a, 4]

数组删除数据

1
2
3
const a = [1,2,3]
a.splice(1, 1) // 不符合im
const b = a.filter(n => n!=2)

数组修改数据

1
2
3
const a = [1,2,3]
a[1] = 4 // 不符合im
const b = [...a.slice(0, 1), 4, ...a.slice(2)]

Redux三个原则(原理)

  1. 单一数据源(store):整个应用的state被存储在一棵object 树中,并且这个object树只存在于唯一一个store中。
  2. store上的state是只读:不能直接修改state,唯一修改state的方法是发送action给store(action是一个用于描述发生事件的普通对象)。
  3. 用纯函数来执行state的修改:为了实现action改变state,要编写reducer。

单向数据流(one-way data flow)

  • 用state描述应用程序在特定时间点的状态
  • 用state来渲染视图(View)
  • 当发生事件(比如用户点击按钮),state会根据发生action进行更新,生成新的state
  • 然后通知相关view重新渲染

state

  • state是javascript的对象(object树)(数字,字符串,布尔值等等都可以成为state)
  • 不能被序列化的数据不要存在state上(function)

action

  • action是把数据从view传给store的有效荷载(payload)
  • action是store上数据的唯一来源(dispatch(action))
  • action是javascript对象,必须有type属性表示它执行的动作,除了此之外action其他属性由开发者自己决定(通常数据放在payload里)

action创建函数(action creator):返回action对象的函数

1
2
3
4
const addTodo = (text) => ({
type: 'addTodo',
payload: {id: getNextId(), text, completed: false}
})

action 的作用只是描述发生事件的事实,它不包含描述如何更新state。

reducer

  • reducer的概念来自于Array.prototype.reduce函数
  • reducer是纯函数
  • reducer处理action,和现有state一起,运算出新的state
  • reducer处理多种action,对于它不能处理的action,直接返回state。
  • reducer的形式 (state=初始值, action) => newState,注意要提供state初始值

组合reducer(reducer可以互相调用)

数组里父级reducer调用子级reducer

1
2
3
4
5
6
7
8
9
10
function counters(state=[], action) {
switch(action.type) {
case 'addCounter':
return ...
case 'incr':
const counterState = state[action.payload.index]
const newCounterState = counter(counterState, action)
...
}
}

合并多个reducer(可以在任何地方合并多次,不要认为只能在顶级reducer合并一次)

1
2
3
4
5
6
7
8
9
10
11
12
import {combineReducers} from 'redux'

const reducer1 =
(state = {count: 0, loading: false}, action) => {...}
const reducer2 =
(state = [], action) => {...}

const reducer = combineReducers({
key1: reducer1,
key2: reducer2
})
// state = {key1: {count: ..., loading: ...}, key2: [...]}

selector函数

和useSelector的参数有关

  • selector是纯函数
  • selector负责将store的state数据转换成渲染需要的数据结构
  • selector函数的形式 (state) => 值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const getVisibleTodos = state => {
// state = {todos: [], filter: '未完成'}
switch(state.filter) {
case '未完成':
return state.todos.filter(todo => !todo.completed)
case '已完成':
return state.todos.filter(todo => todo.completed)
case '全部':
default:
return state.todos
}
}
const todos = useSelector(getVisibleTodos)

const getCounter = (state, index) => {}
const counter = useSelector((state/* store上顶级的state */) => getCounter(state, index))

createStore方法

1
2
3
4
5
6
7
8
9
10
import {createStore} from 'redux'

const store = createStore(
reducer, [初始状态 initState], [增强器 enhancer]
)
// 如果第二个参数是函数,它就把它当成增强器
// 否则当成初始状态使用
const store = createStore(
reducer, applyMiddleware(...)
)

中间件 middleware

中间件:给dispatch增加额外功能,比如输出log,处理异步请求

1
2
3
4
5
import {createStore, applyMiddleware} from 'redux'
const store = createStore(
reducer,
applyMiddleware(中间件1, 中间件2, ...) // 有可能顺序的配置会影响使用
)

redux-thunk

thunk:函数实现“传名调用”,用函数返回另外一个函数,固定一部分参数,返回临时函数就叫作thunk函数。

redux-thunk 库让dispatch(本来dispatch只能发送action,也就是普通的object)可以发送函数作为参数 (dispatch, getState) => {}

thunk action creator的形式:

1
2
3
const thunkAction = (...参数) => (dispatch, getState) => {
...
}


RTK

创建项目

1
npx create-react-app 项目名字 --template redux

如果已有的react项目

1
npm i @reduxjs/toolkit react-redux

slice 切片

利用createSlice,集中创建了跟一种应用相关数据的reducer, selectors和actions。

features 功能

RTK中推荐把和应用有关的数据、组件都放在一个文件结构下,这些模块共同形成的应用功能称为feature。

configureStore

替代createStore函数,使用比以前简单,自动集成了thunk中间件和开发工具。

1
2
3
4
5
6
7
8
9
import {configureStore} from '@reduxjs/toolkit'

const store = configureStore({
reducer: { // 省略自己调用combineReducer
key1: reducer1,
key2: reducer2,
...
}
})

configureStore自定义middleware的方式:

1
2
3
4
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger),
})

createSlice

通过slice对象创建reducer和actions,并且可以处理外部的actions。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const slice = createSlice({
name: 'todos', // 用来作为自动生成action的type前缀
initialState: [], // 初始状态
reducers: {
addTodo(state, {payload}) {
// 因为有immer支持,所以可以直接用修改内容的方式修改state
// 如果用immer方式,不需要写return
// 如果state不是引用类型(是string/number/boolean),需要return
state.push({...payload, completed: false})
// 仍然可以用immutability的方式返回数据
// return [...state, newTodo]
},
... // 这种function对照经典写法的case 'todos/addTodo'
}
})

export const {reducer: todosReducer} = slice // 导出reducer
export const {addTodo, ...} = slice.actions // 导出action

createAsyncThunk

生成一个异步的action,会自动发送pending/fulfilled/rejected的action

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
export const myThunkAction = createAsyncThunk(
'myThunk', // action前缀
// 生成其他三个action:myThunk/pending, myThunk/fulfilled, myThunk/rejected
async () => {
const data = await fetchAbc(...)
const result = ...
return result
}
)

const slice = createSlice({
...,
extraReducers: {
[myThunk.pending](state, action) {
// ....
},
[myThunk.fulfilled](state, action) {
// ....
},
[myThunk.rejected](state, action) {
// ....
}
}
})

RTK对Redux使用的优化

  • 用configureStore替代createStore方法,使创建store的过程更加简单,自动集成了必要的中间件(thunk和devtools)
  • 创建slice时必须给配置默认状态,避免了手工创建reducer的时候忘记给state默认值的情况。
  • 使用slice创建reducer的同时创建相关的action,减少手动创建action creator的模版代码,使用reducers对象替代switch语句。
  • 使用immer库,使reducer里可以用非immutable的风格书写纯函数,使得修改数据的书写效率和程序结构都得到大幅度的改善。
  • 使用createAsyncThunk函数创建异步方法,减少手动创建thunk action的复杂度。