React(六)
- 一、Redux测试项目搭建
- 1.创建store仓库
- 2.创建reducer函数(纯函数)
- 3.constants.js保存action名字
- 4.修改store中的数据
- 5.动态生成action
- 二、React中如何使用redux
- 1.安装redux
- 2.创建store
- 3.组件中订阅store
- 4.派发action修改store数据
- 三、第三方库react-redux简化代码
- 1.省掉手动订阅的步骤
- 2.同步派发action
- 3.redux如何进行异步操作
- (1)在组件中派发异步action
- (2)在actionCreators中派发异步action
- 4.redux的devtools怎么打开
- 四、redux模块化
- 五、Redux ToolKit的使用
- 1.三个核心的API
- 2.用更简洁的代码创建store
- (1)configureStore创建大仓库store
- (2)createSlice创建小仓库
- (3)将小仓库添加到大仓库中
- 3.组件中使用和修改仓库的数据
- 4.RTK的异步请求写在哪里
一、Redux测试项目搭建
新建一个文件夹Redux,里面建个src文件夹。先看一下最终的项目结构:
src目录下开启终端执行:
npm init
npm add redux
src目录下启动:
node 文件名
我们一点一点来看这个redux的使用过程:
1.创建store仓库
由于我们是node,所以导包用的不是import(这里没学过node,先不管,反正是import那个意思)
在index.js
中:
const { createStore } = require('redux');
const {reducer} = require('./reducer');// 创建的store
const store = createStore(reducer);module.exports = store;
其中第一行是导入这个创建store的函数,然后reducer我们在另一个文件中引入:
2.创建reducer函数(纯函数)
纯函数就是在这个函数中不能对传进来的参数内部的东西(属性)进行修改。
第一行是引入的action
名字,先不管。
1、第一步是定义一个初始化数据。
2、第二步是创建reducer
函数,该函数有两个参数,第一个state
是目前保存的旧state数据,第二个参数action
是别的地方dispatch
传过来的需要更新的数据
3、render
函数里面主要是找到对应的action,然后修改数据并返回一个新的浅拷贝的state
对象。
4、如果没有对应的aciton
的话,就返回默认的初始state
const {CHANGENAME, CHANGEAGE} = require('./constants');//初始化数据
const initialState = {name: 'zzy',age: 18
}//定义reducer函数:纯函数
//参数1:store目前保存state
//参数2:本次需要更新的action(dispatch传过来的)
//返回值:作为store之后存储的state
function reducer(state = initialState, action) {// console.log('reducer', state, action)// reducer { name: 'zzy', age: 18 } { type: 'changeName', name: 'ht' }//1.第一种,if-else// if (action.type === 'changeName') {// return { ...state, name: action.name };// }// if(action.type === 'changeAge') {// let newState = {...state};// newState.age = action.age;// return newState;// }// return state;//2.第二种,switch-caseswitch(action.type) {case CHANGENAME:return { ...state, name: action.name };case CHANGEAGE:let newState = {...state};newState.age = action.age;return newState;default: return state}
}module.exports = {reducer};
这里呢有个没见过的写法,先学习一下:
const obj = {name: 'zzy', age: 18};
const copy = {...obj, name:'ht'};
console.log(copy);//{name: 'ht', age: 18}
上面的代码中,是浅拷贝一个obj,并把里面的name替换成ht
其实本质上是:
const obj = {name: 'zzy', age: 18};
const copy = {...obj};
copy.name = 'ht'
console.log(copy);//{name: 'ht', age: 18}
最后我们再来看第一行是什么,其实这里的action的名字,只不过我们把名字单独写到另一个js文件引入进来,避免一些名字写错的问题。
3.constants.js保存action名字
这里的名字是为了action自己写名字的时候不小心写错,需要在两个地方引用这个变量:
1、reducer函数中
2、dipatch的地方()
const CHANGENAME = 'changeName';
const CHANGEAGE = 'changeAge';
module.exports = {CHANGENAME,CHANGEAGE
}
4.修改store中的数据
上面这些搞定了,接下来我们来看看如何修改store中的数据:
某个js文件:
const store = require('./store');
console.log(store.getState());
//修改store中的数据
store.dispatch({type: 'changeName', name: 'ht'});
console.log(store.getState());
store.dispatch({type: 'changeAge', age: 30});
console.log(store.getState());
把store引入进来,然后dispatch
,传入一个对象,该对象有两个属性,type
是固定的,值是action的名字;name
是要修改的属性值。通过store.getState()
可以读取当前仓库state
的值。
但是这样的话你会发现每次改都要手动输出修改的值,这样很麻烦,所以我们可以通过store.subscribe
订阅数据的改变,这样的话store中数据一旦改变,就会执行回调,该回调中通过store.getState()
读取的是改变后的新值。
const store = require('./store');//订阅数据的改变
const unsubscribe = store.subscribe(() => {console.log('数据变化了,变成了:', store.getState());
})//修改store中的数据:必须action
store.dispatch({ type: 'changeName', name: 'ht' });
//一般在销毁钩子中取消订阅
unsubscribe();
store.dispatch({ type: 'changeAge', age: 30 });
当然啊,如果在框架中用的话,最好在销毁钩子中取消订阅,方法就是调用这个玩意儿。
5.动态生成action
如果有多个dispatch:
store.dispatch({ type: CHANGENAME, name: 'ht' });
store.dispatch({ type: CHANGEAGE, age: 30 });
store.dispatch({ type: CHANGEAGE, age: 40 });
store.dispatch({ type: CHANGEAGE, age: 50 });
store.dispatch({ type: CHANGEAGE, age: 60 });
store.dispatch({ type: CHANGEAGE, age: 70 });
那么每次dispatch都要传一个对象,然后写触发哪个action,要改什么,这真的是非常麻烦,所以最后一个关键文件就是:actionCreators.js
用来封装每个dispatch的值:
二、React中如何使用redux
1.安装redux
在目录下创建一个脚手架
create-react-app 项目名
安装redux,在脚手架目录下:
npm install redux 或 npm add redux
2.创建store
创建store文件夹,里面先来两个文件:index.js
和reducer.js
index.js
import {createStore} from 'redux';
import reducer from './reducer';let store = createStore(reducer);export default store;
reducer.js
let initialState = {counter: 1
}
function reducer(state = initialState, action) {switch(action.type) {default:return state;}
}export default reducer;
再创建两个空文件constants.js
和actionCreators.js
备用
3.组件中订阅store
1、组件中我们如果想要去用store中的数据,可以在state中直接通过store.getState().属性名
去读取一个初始值。但是如果store数据改变,这里不会跟着变。
2、如果想要store数据改变时组件能跟着变,那么我们需要在componentDidMount
钩子中去订阅store
,每次数据改变,就会执行回调,回调中我们调用setState
重新读取store,组件就能够实现和store数据的同步。
import React, { PureComponent } from 'react';
import store from './store';
export class App extends PureComponent {constructor() {super();this.state = {counter: store.getState().counter}}componentDidMount() {//订阅storestore.subscribe(() => {// console.log(store.getState());this.setState({counter: store.getState().counter})})}render() {let { counter } = this.state;return (<div><h2>{counter}</h2></div>)}
}export default App;
4.派发action修改store数据
比如我们定义一个按钮用来控制store
中的counter+1
,那么:
1、回到第一步的空文件constants.js
中,我们来用变量存储action
的名字
export const CHANGECOUNTER = 'changeCounter';
2、回到空文件actionCreators.js
中,我们定义函数来格式化action
对象
import {CHANGECOUNTER} from './constants'
export const change = (num) => {return {type: CHANGECOUNTER,num: num}
}
3、再回到组件中:
当我们点击的时候就派发action,action对象由上面定义好的函数来生成。
import React, { PureComponent } from 'react';
import store from './store';
import {change} from './store/actionCreators'
export class App extends PureComponent {constructor() {super();this.state = {counter: store.getState().counter}}componentDidMount() {//订阅storestore.subscribe(() => {// console.log(store.getState());this.setState({counter: store.getState().counter})})}change(num) {store.dispatch(change(num));}render() {let { counter } = this.state;return (<div><h1>App</h1><h2>{counter}</h2><button onClick={() => this.change(1)}>点击+1</button><Son /></div>)}
}export default App;
使用流程大概就是这样。
三、第三方库react-redux简化代码
这个库是专门用于简化在react中使用redux的。
安装:
npm install react-redux
1.省掉手动订阅的步骤
这里是react-redux的使用步骤
在src的index.js中引入Provider,包裹App组件并传入我们定义好的store,这样的话后代组件就可以访问store
定义一个子组件Son,引入connect
函数,该函数返回一个高阶组件,高阶组件本身是一个函数,调用该函数可以给Son注入东西。connect
的参数是一个回调mapStateToProps
,顾名思义就是把store
中的state
通过高阶组件注入到Son的props
中,具体代码如下:
import React, { PureComponent } from 'react';
import store from '../store';
import {connect} from 'react-redux';export class Son extends PureComponent {render() {let { Soncounter } = this.props;return (<div><h1>Son</h1><h2>{Soncounter}</h2></div>)}
}//调用connect返回一个高阶组件(函数)
//高阶组件中传入我们要注入store的组件
const mapStateToProps = (state) => {//返回一个对象,这样可以读到store中state的数据,并把这个数据// 通过connect()返回的高阶组件注入到Son的props中return {//注入给Son的props.SoncounterSoncounter: state.counter }
}
export default connect(mapStateToProps)(Son);
2.同步派发action
connect的第二个参数也是一个函数mapDispatchToProps
,参数是一个dispatch函数。
mapDispatchToProps
可以向组件中注入一些函数,这些函数是用来派发action的。
组件可以通过props接收函数并调用传参,把要修改的值传过去,然后通知仓库修改数据。
3.redux如何进行异步操作
先安装axios
npm install axios
(1)在组件中派发异步action
如果我们要在组件中异步请求数据,然后把数据保存到store中,按照react-redux
库的步骤来说是这样的:
1、constants.js
中定义action
的名字:
2、actionCreators.js
中定义创建action对象的函数:
3、reducer.js
中定义接收数据后对应的更改操作
4、去组件中的componentDidMount中发送网络请求并通知store更新数据。
(2)在actionCreators中派发异步action
如果发送请求的组件根本不用这个数据呢?发请求只是为了把数据存到store中,那这样写在挂载钩子里就不太好。所以我们可以把异步的过程放到redux中去处理。
这里我们用到一个中间件redux-thunk:
npm install redux-thunk
这个中间件可以让dispatch的参数是一个函数的调用
,那么我们可以在这个函数里进行异步操作,得到结果后就再执行dispatch传入action对象
。
使用步骤:
1、在store/index.js
中传入createStore的第二个参数,意思是使用中间件,参数传我们刚才安装的thunk。这样的话dispatch就可以派发一个函数了。
2、在actionCreators.js
中把之前的函数返回值从一个action对象换成一个函数,在函数中请求到数据后再次dispatch一个action对象:{type: xxx, newsList: red.data}
3、回到组件中,直接把dispatch改成一个函数的调用
ok,异步请求数据就是这么搞的。
4.redux的devtools怎么打开
安装直接去浏览器插件库里一搜就有。
在开发环境下如果想要打开,需要在store/index.js
中:
import {createStore, applyMiddleware, compose} from 'redux';
import reducer from './reducer';
import thunk from 'redux-thunk';const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;let store = createStore(reducer, composeEnhancers(applyMiddleware(thunk)));export default store;
当然这是由中间件thunk的情况下,其他情况请见github:
https://github.com/zalmoxisus/redux-devtools-extension
四、redux模块化
类似vuex中的namespace,不过比那个麻烦一些。
👇👇👇👇👇👇
除了把每部分的数据拆开,每个小仓库的index.js
只需要把reducer函数导出
import reducer from './reducer';
export default reducer;
然后在大仓库中,通过combineReducers
把小仓库的reducer
合并成一个大reducer
并给每个小仓库起个名字。
其他需要注意的就是路径的问题了。还有就是读取仓库数据的时候,需要加上小仓库的名字:
五、Redux ToolKit的使用
Redux ToolKit 中文官方文档
安装:npm install @reduxjs/toolkit
。
如果没安react-redux
也要带着一起安装:npm install react-redux @reduxjs/toolkit
1.三个核心的API
configureStore: 包装旧的createStore
以提供简化的配置选项和良好的默认值。它可以自动组合你的 slice reducer
,添加你提供的任何 Redux 中间件,redux-thunk默认包含
(这个东西就是让dispatch可以是一个函数的调用的中间件),并启用 Redux DevTools Extension
(通过某个地方写api可以直接设置devtool是否启用,不用我们再window.xxxxx了)。
createSlice: 接受reducer函数
的对象、切片名称
和初始状态值
,并自动生成切片reducer
,并带有相应的actions
。
createAsyncThunk: 接受一个动作类型字符串
和一个可以写异步的函数
,并生成一个包含pending/fulfilled/rejected
的对象,我们可以通过某种方式监听状态的改变。
2.用更简洁的代码创建store
(1)configureStore创建大仓库store
在store/index.js
中,我们先创建一个store
import {configureStore} from '@reduxjs/toolkit';const store = configureStore({reducer: {待填写......},devTools: true, //默认就是true
})export default store;
传入的配置对象中,有如下三个常见的参数:
-
reducer:将slice中的reducer可以组成一个对象传入此处;
-
middleware:可以使用参数,传入其他的中间件(自行了解);
-
devTools:是否配置devTools工具,默认为true;
(2)createSlice创建小仓库
一般我们会在store/modules
里创建小仓库:
比如login.js
:
import {createSlice} from '@reduxjs/toolkit';const loginSlice = createSlice({name:'login',initialState: {counter: 99},//相当于reducer函数reducers: {//相当于每个caseaddNumber(state, action) {//这里可以直接改不用浅拷贝,默认给你拷贝好了state.counter = action.payload;}}
})
//reducers里的方法会自动放在counterSlice的actions属性中,用于派发action
export const { addNumber } = loginSlice.actions;
//注意导出的是reducer,用于在index.js中对reducer进行组合
export default loginSlice.reducer
在createSlice
的配置对象中,一般有如下几个配置项:
-
name:用户标记slice的名词,在之后的redux-devtool中会显示对应的名词;
-
initialState:初始化值,第一次初始化数据时的值;
-
reducers:相当于之前的reducer函数,是对象类型,对象中可以添加很多的函数;
函数类似于redux原来reducer中的一个case语句
,该函数的参数如下:
参数一:
state
, 当前的state状态
参数二:传递的actions参数
, actions有两个属性,一个是自动生成的type
, 另一个是传递的参数放在payload中
;
createSlice返回值是一个对象,包含所有的actions
,通常我们会把每个action暴露出去;
(3)将小仓库添加到大仓库中
回到index.js
,把创建的login
的reducer
函数导出来,添加到大仓库中
import {configureStore} from '@reduxjs/toolkit';
//导入reducer
import loginReducer from './modules/login';const store = configureStore({reducer: {login: loginReducer,},devTools: true, //默认就是true
})export default store;
这样的话, redux就搭建完成了,接下来我们去组件里使用
3.组件中使用和修改仓库的数据
这里要用到react-redux这个库,前边已经安装过了,需要配置的地方和之前差不多:
点击查看如何建立redux和react的连接
这样我们来到logIn.jsx
组件中,就可以去使用仓库中的counter
:
使用方式还是没什么太大区别的嗷。修改的话也是一样,看上面那个图,其实也是通过connect
把dispatch
的方法注入props
,然后调用派发action
。
这个React ToolKit
在这里简化了哪些东西呢?我感觉是简化了我们自己生成action
对象的操作,还有用变量存储type
名字的操作,以前我们需要很繁琐的操作(先生成action对象再派发)。
但是现在!我们派发可以直达小仓库的这个函数(可以直接导入进来)
import {createSlice} from '@reduxjs/toolkit';const loginSlice = createSlice({name:'login',initialState: {counter: 99},//相当于reducer函数reducers: {//相当于每个caseaddNumber(state, action) {console.log(action);//{type: 'login/addNumber', payload: 1}//这里可以直接改不用浅拷贝,默认给你拷贝好了state.counter = action.payload;}}
})
//reducers里的方法会自动放在counterSlice的actions属性中,用于派发action
export const { addNumber } = loginSlice.actions;
//注意导出的是reducer,用于在index.js中对reducer进行组合
export default loginSlice.reducer
import { addNumber } from '../store/modules/login';
而且输出aciton会发现type
是自动生成了个东西login/addNumber
,不用再担心自己会写错名字了。传过来的数值1存到payload
中。
最关键的!我们可以直接通过state
参数去修改仓库数据,不用再向之前一样先浅拷贝再修改了(底层做了处理),确实方便了许多。
4.RTK的异步请求写在哪里
可以像以前一样,写在挂载钩子中,写法一样,但是还是那个意思,组件里发请求不太好。所以RTK给我们封装的话我们异步在redux中如何写?
像上面一样,创建另一个小仓库home
这里就要用到第三个API:createAsyncThunk
,调用它返回一个函数,我们把函数暴露出去,在组件中引入并在dispatch
中传入该函数的调用
,即可回来执行函数的第二个参数(发请求的回调)
import {createSlice, createAsyncThunk} from '@reduxjs/toolkit';
import axios from 'axios';//异步操作的处理
export const reqNewsList = createAsyncThunk('reqData', async () => {const result = await axios.get('https://ku.qingnian8.com/dataApi/news/newslist.php')return result.data;
})const homeSlice = createSlice({name: 'home',initialState: {newsList: ['无数据'],},reducers: {getNewsList(state, {payload}) {console.log(payload);state.newsList = payload;}},//监听异步的状态变化extraReducers: {[reqNewsList.pending](state, {payload}) {console.log('异步处于pending状态');},[reqNewsList.fulfilled](state, {payload}) {console.log(payload);state.newsList = payload;},[reqNewsList.rejected](state, {payload}) {console.log('异步处于rejected状态,有错误!');},}
})export const {getNewsList} = homeSlice.actions;
export default homeSlice.reducer;
异步请求到数据后,把数据返回出来,我们通过createSlice配置对象中另一个配置项extraReducers可以监听异步请求的变化:
- 如果请求中就调用pending
- 请求到了就调用fulfilled并把返回值用payload接收,我们就可以拿到异步请求的数据了;
- 如果有错误,就会执行rejected的回调。
组件:
搞定