日期:2024年10月22日

Redux Toolkit(RTK)

上边的案例我们一直在使用Redux核心库来使用Redux,除了Redux核心库外Redux还为我们提供了一种使用Redux的方式——Redux Toolkit。它的名字起的非常直白,Redux工具包,简称RTK。RTK可以帮助我们处理使用Redux过程中的重复性工作,简化Redux中的各种操作。

在React中使用RTK

安装,无论是RTK还是Redux,在React中使用时react-redux都是必不可少,所以使用RTK依然需要安装两个包:react-redux和@reduxjs/toolkit。

npm

npm install react-redux @reduxjs/toolkit -S

yarn

yarn add react-redux @reduxjs/toolkit

修改上边的例子

使用RTK时,reducer依然可以使用之前的创建方式不变,但是不在需要合并reducer。RTK为我们提供了一个configureStore方法,它直接接收一个对象作为参数,可以将reducer的相关配置直接通过该对象传递,而不再需要单独合并reducer。

上例中代码:

const reducer = combineReducers({
stu:stuReducer,
school:schoolReducer
});

const store = createStore(reducer);

修改为:

const store = configureStore({
reducer:{
stu:stuReducer,
school:schoolReducer
}
});

configureStore需要一个对象作为参数,在这个对象中可以通过不同的属性来对store进行设置,比如:reducer属性用来设置store中关联到的reducer,preloadedState用来指定state的初始值等,还有一些值我们会放到后边讲解。

reducer属性可以直接传递一个reducer,也可以传递一个对象作为值。如果只传递一个reducer,则意味着store中只有一个reducer。若传递一个对象作为参数,对象的每个属性都可以执行一个reducer,在方法内部它会自动对这些reducer进行合并。

RTK的API

createAction(一般不直接用)

action是reducer中的第二个参数,当我们通过dispatch向reducer发送指令时需要手动创建action对象并传递。action中常见的属性有两个一个是type用来指定操作的类型,一个是payload用来指定要传递的数据。

RTK为我们提供了一个方法createAction,用来帮助我们创建action。

createAction(type, prepareAction?)

它的第一个参数为type,用来指定action中的type属性。第二个参数可选先忽略它。它的返回值是一个函数。我们可以这么调用:

conconst setName= createAction('ADD');

setName(); // {type: 'ADD', payload: undefined}
setName('猪八戒'); // {type: 'ADD', payload: '猪八戒'}

返回值的函数我们可以调用,调用该函数后会得到一个对象,这个对象有两个属性type和payload,type属性值就是我们调用createAction传递的第一个参数,上例中type就是’ADD’。而payload属性就是我们调用该函数时传递的参数。

const add = createAction('SET_NAME');
add(); // {type: 'SET_NAME', payload: undefined}
add('猪八戒'); // {type: 'SET_NAME', payload: '猪八戒'}

简单说,createAction会返回一个函数,这个函数可以用来创建固定type属性值的对象,并且这个函数的第一个参数会成为新建对象的payload属性值。

可以通过creatAction修改之前的项目:

先创建四个action函数:

const setName = createAction('SET_NAME');
const setAge = createAction('SET_AGE');
const setAddress = createAction('SET_ADDRESS');
const setGender = createAction('SET_GENDER');

修改dispatch

dispatch(setName('猪八戒'));
dispatch(setAge(28));
dispatch(setGender('女'));
dispatch(setAddress('高老庄'));

createAction返回函数所创建的对象结构是固定的{type:'xxx', payload:...},我们也可以通过向createAction传递第二个参数来指定payload的格式:

const add = createAction('ADD', (name, age, gender, address) => {
    return {
        payload:{
            name,
            age,
            gender,
            address
        }
    }
});

add('沙和尚', 38, '男', '流沙河'); // {"type":"ADD","payload":{"name":"沙和尚","age":38,"gender":"男","address":"流沙河"}}

createReucer(一般不用)

该方法用来是创建reducer的工具方法。

createReducer(initialState, builderCallback)

参数:

initialState —— state的初始值

builderCallback —— 带有builer的回调函数,可以同builer来设置reducer的逻辑

回调函数中会传递一个builder作为参数,通过通过builder可以将action和函数进行绑定,使用时可以通过传递指定的action来触发函数的调用。

builder有一个常用的方法addCase,addCase需要两个参数,第一个参数为action,第二个参数为回调函数。action直接传递通过createAction所创建的函数即可,第二个参数是一个回调函数,回调函数类似于reducer,第一个参数为state,第二个参数为action。但又和reducer不同,该回调函数中返回的state是一个代理对象,可以直接对该对象修改,RTK会自动完成其余操作。

示例:

// 创建action
const setName = createAction('setName');

// 创建reducer
const stuReducer = createReducer({
        name: '孙悟空',
        age: 18,
        gender: '男',
        address: '花果山'
    }, builder => {
        // 通过builder将action和回调函数进行绑定
        builder.addCase(setName, (state, action) => {
            // 这里的state是代理对象,可以直接对其进行修改
            state.name = action.payload;
        });
    }
);

// 配置reducer
const store = configureStore({
    reducer: {
        stu: stuReducer,
        school: schoolReducer
    }
});

// 发送指令修改name属性
dispatch(setName('猪八戒'));

无论是createAction和createReducer都不是RTK中的常用方式(要是这么写代码,可能得疯)。介绍他们只是希望你能了解一下RTK的运行方式。对于我们来创建reducer时最最常用的方式是:createSlice。

createSlice

createSlice是一个全自动的创建reducer切片的方法,在它的内部调用就是createAction和createReducer,之所以先介绍那两个也是这个原因。createSlice需要一个对象作为参数,对象中通过不同的属性来指定reducer的配置信息。

createSlice(configuration object)

配置对象中的属性:

initialState —— state的初始值

name —— reducer的名字,会作为action中type属性的前缀,不要重复

reducers —— reducer的具体方法,需要一个对象作为参数,可以以方法的形式添加reducer,RTK会自动生成action对象。

示例:

const stuSlice= createSlice({
    name:'stu',
    initialState:{
        name: '孙悟空',
        age: 18,
        gender: '男',
        address: '花果山'
    },
    reducers:{
        setName(state, action){
            state.name = action.payload
        }
    }
});

createSlice返回的并不是一个reducer对象而是一个slice对象(切片对象)。这个对象中我们需要使用的属性现在有两个一个叫做actions,一个叫做reducer。

actions

切片对象会根据我们对象中的reducers方法来自动创建action对象,这些action对象会存储到切片对象actions属性中:

stuSlice.actions; // {setName: ƒ}

上例中,我们仅仅指定一个reducer,所以actions中只有一个方法setName,可以通过解构赋值获取到切片中的action。

const {setName} = stuSlice.actions;

开发中可以将这些取出的action对象作为组件向外部导出,导出其他组件就可以直接导入这些action,然后即可通过action来触发reducer。

reducer

切片的reducer属性是切片根据我们传递的方法自动创建生成的reducer,需要将其作为reducer传递进configureStore的配置对象中以使其生效:

const store = configureStore({
reducer: {
stu: stuSlice.reducer,
school: schoolReducer
}
});

总的来说,使用createSlice创建切片后,切片会自动根据配置对象生成action和reducer,action需要导出给调用处,调用处可以使用action作为dispatch的参数触发state的修改。reducer需要传递给configureStore以使其在仓库中生效。

完整代码:

import ReactDOM from 'react-dom/client';
import {Provider, useDispatch, useSelector} from "react-redux";
import {configureStore, createSlice} from "@reduxjs/toolkit";

const stuSlice = createSlice({
name: 'stu',
initialState: {
name: '孙悟空',
age: 18,
gender: '男',
address: '花果山'
},
reducers: {
setName(state, action) {
state.name = action.payload;
},
setAge(state, action) {
state.age = action.payload;
},
setGender(state, action) {
state.gender = action.payload;
},
setAddress(state, action) {
state.gender = action.payload;
}
}
});

const {setName, setAge, setGender, setAddress} = stuSlice.actions;

const schoolSlice = createSlice({
name: 'school',
initialState: {
name: '花果山一小',
address: '花果山大街1号'
},
reducers: {
setSchoolName(state, action) {
state.name = action.payload;
},
setSchoolAddress(state, action) {
state.address = action.payload;
}
}
});

const {setSchoolName, setSchoolAddress} = schoolSlice.actions;

const store = configureStore({
reducer: {
stu: stuSlice.reducer,
school: schoolSlice.reducer
}
});

const App = () => {
const stu = useSelector(state => state.stu);
const school = useSelector(state => state.school);
const dispatch = useDispatch();
return <div>
<p>
{stu.name} -- {stu.age} -- {stu.gender} -- {stu.address}
</p>
<div>
<button onClick={() => {
dispatch(setName('猪八戒'));
}}>改name
</button>
<button onClick={() => {
dispatch(setAge(28));
}}>改age
</button>
<button onClick={() => {
dispatch(setGender('女'));
}}>改gender
</button>
<button onClick={() => {
dispatch(setAddress('高老庄'));
}}>改address
</button>
</div>

<hr/>

<p>
{school.name} -- {school.address}
</p>
<div>
<button onClick={() => {
dispatch(setSchoolName('高老庄中心小学'));
}}>改学校name
</button>
<button onClick={() => {
dispatch(setSchoolAddress('高老庄中心大街15号'));
}}>改学校address
</button>
</div>
</div>;
};


const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<div>
<Provider store={store}>
<App/>
</Provider>
</div>
);

目前我们的代码都是编写在同一个文件中的,真实开发中还需要对代码进行拆分,不写了你们自己试一试吧,具体内容看视频。

5 10 投票数
文章评分
订阅评论
提醒
guest

9 评论
最旧
最新 最多投票
内联反馈
查看所有评论
3164451260@qq.com
3164451260@qq.com
2 年 前

超哥能讲讲React中数据持久化吗

wuhua
1 年 前

这就是数据持久化啊

熊zzz
熊zzz
2 年 前

超哥,如果两个切片的actions中有同名的方法setName,哪怎么办呢,我只能说保证方法名不同吗

Chin
2 年 前

打卡

你的寒王
你的寒王
2 年 前

超哥,是不是rtk可以直接在dispatch的方法里发请求然后通过切片的switch修改store,还需要像以前一样使用中间件thunk吗?

3164451260@qq.com
3164451260@qq.com
2 年 前

超哥能讲讲reduex中如何处理异步操作不, 比如发送请求

9
0
希望看到您的想法,请您发表评论x