Redux简明上手指南——react-redux和Redux Toolkit两种写法
介绍
本文是一篇写给给刚熟悉react后想要快速上手redux的开发者们的文章,你需要简单了解react组件以及props等基本用法。本文的信息均源自于redux和Redux Toolkit的官方网站:
本文主要包括:
- Redux核心概念介绍
- 仅使用react-redux构建应用
- 使用Redux Toolkit构建应用(推荐)
Redux核心概念
redux中核心的概念包括:action、reducer以及store
动机
我们使用redux是为了更加方便地管理某些state,尤其是在多个组件同时依赖于同一个state时。Redux为我们提供了一个能够跨组件管理state的方式。
action
action代表一种将要修改state的动作,本质是一个有着type属性的Javascript对象。type属性值是一个字符串,作为此action的标识。 在action中可以包含一些数据信息,通常放在payload中,例如:
const addTodoAction = {
type: 'todos/todoAdded',
payload: 'Buy milk'
}
reducer
reducer是一个函数,接收两个参数当前的state和一个action,返回值为新的state:(state, action) => newState
实际对state的修改操作都是在reducer中发生的,但是注意若state为一个对象,绝对不能修改原来的state对象,而是要对原对象进行浅拷贝,然后操作这个新对象之后再返回。这一点十分关键,如果不遵守会产生难以预料的错误。但是如果使用Redux Toolkit的话就不必额外担心这一点,这也是为什么推荐使用Redux Toolkit来构建应用。
另外reducer应该是一个‘纯函数’,这意味着在reducer中不应该有任何的异步逻辑或者产生其他'副作用',只能够对state进行操作。
store
store是当前管理的state的容器,可以通过传入一个reducer进行创建。其身上有一个getState
方法,可以获取当前管理的state的值。还有一个dispatch
方法,通过传入一个action对象,然后经过reducer处理来修改state的值,这也是修改state的唯一方法。
使用react-redux构建应用
以官网的计数器的案例为例子。
首先需要在项目src目录下新建文件夹命名为redux,然后在其中新建三个文件:actions.js、reducer.js和store.js
在actions.js中编写以下内容:
// actions.js
export const increment = 'INCREMENT'
export const decrement = 'DECREMENT'
/* 我们不希望手动创建
{
type: 'INCREMENT',
payload:1
}
这样的action对象
一般都是通过一个函数生成这样的对象
这个函数称为action creator
*/
// 创建increment这个action的函数
export const incrementCreator =
(payload) => {type: increment, payload}
export const decrementCreator =
(payload) => {type: decrement, payload}
然后在reducer.js中编写以下内容
// reducer.js
import {increment, decrement} from './actions.js'
// state初始值
const initState = 0
// reducer函数第一个参数为旧的state,第二个参数为传入的action对象
// 默认暴露reducer函数
export default (state=initState, action) => {
// 使用if 或者 switch语句判断action的type类型,然后操作state,返回新的state,注意不能直接修改原state对象!
if (action.type === increment) {
return state + 1
}
if (action.type === decrement) {
return state - 1
}
return state
}
然后在store.js中编写以下内容
// store.js
import { createStore } from 'redux';
import reducer from './reducer.js'
// 使用createStore函数创建store,参数为刚刚定义的reducer
// 默认暴露创建好的store
export default createStore(reducer)
至此我们的store就已经准备好了,接下来就是在组件中使用了。下面的写法如果不能直接理解也没有关系,在实际使用过后就会有新的理解。
使用Provider组件传递store
在文档中不推荐直接引入store对象,而是要在根组件外使用Provider来传递store。
接下来进入/src/index.js编写以下内容:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { Provider } from 'react-redux';
import store from './redux/store.js'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
{// 注意看这里,使用props的方式将store传入Provider组件}
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
编写UI组件和数据组件
我们需要在编写UI组件之后再编写一个数据组件,最后的目的是数据组件当做UI组件的父组件,由数据组件来接收数据,然后将数据和修改数据的方法通过props的方式传给UI组件,这样写的好处是能够将UI和数据分离开。
UI组件放在/src/components文件夹下,数据组件放在/src/containers文件夹下。
在数据组件中编写以下内容,注意其中connect函数的用法:
import {connect} from 'react-redux'
import Counter from '../../components/counter'
import {increament, decreament} from '../../redux/actions'
// 由于不是以组件标签的形式调用UI组件,因此不能直接通过标签属性的方式传递props
// 需要通过两个函数mapStateToProps和mapDispatchToProps,分别将数据和修改数据的方法以props的方式传递给UI组件
// mapStateToProps传递数据,接收的参数为store中的state,返回一个对象
// 在这个对象中键为props中接收的键名,值为其属性值
const mapStateToProps = (state) => {
return {count: state}
}
// mapDispatchToProps传递操作数据的方法,接收的参数为dispatch方法,用于触发action,同样返回一个对象
// 在这个对象中键为UI组件接收的方法名,值应为一个dispatch某个acion的函数
const mapDispatchToProps = (dispatch) => {
return {
jia: (num) => dispatch(increament(num)),
jian: (num) => dispatch(decreament(num))
}
}
// 在这里调用connect函数,首先传入刚才定义的两个函数,在返回的函数中传入UI组件
// 这样在UI组件中就能够通过props访问到store中维护的state以及操作state的方法了
export default connect(mapStateToProps, mapDispatchToProps)(Counter)
然后在UI组件中编写以下内容:
import React, {useState} from 'react'
export default function Counter(props) {
// 使用ES6解构语法获取props中的数据和方法
const {jia, jian, count} = props
const [num, setNum] = useState(1)
function add(num) {
jia(num*1)
}
function sub(num) {
jian(num*1)
}
function handleChange(e) {
setNum(e.target.value)
}
return (
<div>
<h1>当前的数值是:{count}</h1>
<select defaultValue="1" onChange={(e) => handleChange(e)}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={() => add(num)}>+</button>
<button onClick={() => sub(num)}>-</button>
</div>
)
}
最后别忘记在APP组件中引入编写好的数据组件,千万不要错误地引入UI组件,因为我们是通过数据组件来调用UI组件的。
// App.js
// 注意是container下的counter!
import Counter from './container/counter'
import './App.css';
function App() {
return (
<div className="App">
<Counter/>
</div>
);
}
export default App;
至此我们的计数器案例应该可以正常使用了,但是使用Redux Toolkit我们可以更加轻松和优雅地来编写我们的代码,这也是官方推荐的用法。
使用Redux Toolkit构建应用
使用Redux Toolkit可以在定义Redux时让我们免去许多繁琐的步骤,而UI组件和数据组件则完全不需要修改。
让我们重新进入/src/redux文件夹。
首先是actions.js:
import {createAction} from '@reduxjs/toolkit'
export const increament = createAction('INCREAMENT')
export const decreament = createAction('DECREAMENT')
在这里我们使用createAction
函数来创建action creator,而不需要我们手动地编写工厂函数。此外我们也不需要定义action的type对应的字符串,比如
export const increment = 'INCREMENT'
这句话看起来总是很多余。因此通过createAction
函数创建的action creator函数的toString方法已经被修改了,你可以调用increment.toString()
来获取标识字符串。同时你也可以通过increament.type
来访问action的标识字符串,这在接下来的reducer的编写中提供了很大的方便。
接下来是reducers.js:
import { createReducer } from "@reduxjs/toolkit";
import { increament, decreament } from "./actions";
// createReducer方法为我们自动创建reducer函数
// 只需要我们传入两个参数:
// 第一个参数为state的初始值
// 第二个参数为一个对象,其键为所要处理的action的标识字符串,值为回调函数
// 在回调函数中接收两个参数state——上次的状态以及action——传入的action对象
// 可以通过传入的action对象实现获取payload的数据
export const counter = createReducer(0, {
// 这里使用ES6语法来获取action的标识字符串
// 在[]中的内容将会作为字符串被解析,若不是字符串则会调用其toString方法
[increament]: (state, action) => state + action.payload,
[decreament]: (state, action) => state - action.payload
});
// 官网中还有以下这种写法,不过我个人认为不如第一种写法简洁
// export const counter = createReducer(0, (builder) => {
// builder
// .addCase(increament.toString(), (state, action) => state + action.payload)
// .addCase(decreament.toString(), (state, action) => state - action.payload)
// })
在这里主要的区别就是我们从手动创建reducer函数变为调用createReducer
来创建reducer,因此要注意这个API的使用方法。
另外我们不需要担心对state的操作会破坏数据不可变性。因为在底层redux toolkit已经调用了immer库来保证数据的不可变性。因此这也是为什么推荐使用redux toolkit的原因之一。
最后是store.js:
import {configureStore} from '@reduxjs/toolkit'
import {counter} from './reducers'
export default configureStore({
reducer: counter
})
可以看到与原来几乎没有变化,唯一的区别就是把createStore
函数换成了configureStore
函数,把reducer作为参数对象的一项属性传入进去。
其余的部分不需要更改,现在我们应该可以正常访问计数器案例了。你也可以按照这个框架来构建你自己的redux应用。
总结
- 理解redux的构成要素:action、reducer和store。action本质是一个有type属性的对象,一般通过action creator工厂函数创建,reducer本质是一个函数,在其中包含处理不同action的逻辑,返回值为新的state的值,注意不能够直接修改上一次的state除非你使用redux toolkit的API。store是包含着我们维护的state的容器,外部组件通过store来获取当前的state。修改state只能通过dispatch某个action,然后进入reducer的逻辑中进行修改。
- 推荐使用Redux Toolkit进行构建应用,因为可以使用更加简洁的语法以及避免许多不必要的bug。
- 本文所讲的内容只是最基本的redux的使用方法,其余如slice reducer、异步处理以及中间件等内容请前往官方文档查询。
转载自:https://juejin.cn/post/7181670222406008888