上边的案例我们一直在使用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>
);
目前我们的代码都是编写在同一个文件中的,真实开发中还需要对代码进行拆分,不写了你们自己试一试吧,具体内容看视频。
超哥能讲讲React中数据持久化吗
这就是数据持久化啊
超哥,如果两个切片的actions中有同名的方法setName,哪怎么办呢,我只能说保证方法名不同吗
引入的时候起一个别名就好了
打卡
超哥,是不是rtk可以直接在dispatch的方法里发请求然后通过切片的switch修改store,还需要像以前一样使用中间件thunk吗?
没这么写过,不过我的第一感觉还是不太好,rtk主要任务还是管理store,加载数据之类的并不是它的工作,如果将这些方法在它里边会发生耦合,你维护起来会比较麻烦,中间件的耦合度还是低一些。这只是我暂时的想法,具体你可以试一试看看有没有问题
超哥能讲讲reduex中如何处理异步操作不, 比如发送请求
往后看~