2.redux的基本使用
文章目录 2.redux的基本使用 3.react结合redux 4.react-redux使用5.中间件的使用 6.redux-.redux-saga
在使用react进行开发的过程中,Redux作为一种状态管理库对于我们来讲是十分重要的。但是在我对redux的学习中发现,同样作为状态管理的工具,Vuex会显得更加简单好懂。但是redux的一些思想对我们学习是十分有帮助的
1.初识Redux 1.1 为什么要使用Redux
管理状态,管理什么状态?
实际上随着就前端业务的不断发展不断复杂,前端在业务中需要管理的状态也就越来越多了,比如服务器返回的数据、缓存的数据、用户操作产生的数据,UI的状态信息等等,所有需要我们管理的数据,都需要使用一个机制进行管理。
React是在视图层帮助我们解决了DOM的渲染过程,但是State依然是留给我们自己来管理:
Redux就是一个帮助我们管理State的容器:Redux是的状态容器,提供了可预测的状态管理。
注:实际上redux和react是没有关系的,redux只是一个普通的第三方库,它可以用在任何地方,包括我们原生的项目以及vue、等
1.2 redux的核心理念
Redux要求我们通过来更新数据:
所有的数据的变动都需要通过派发()来进行更新;实际上就是一个的一个普通的对象,用来描述当前的type和;
比如下面就是几个更新数据的:
const action1 = { type: "ADD_FRIEND", info: { name: "lucy", age: 20 } }
const action2 = { type: "INC_AGE", index: 0 }
const action3 = { type: "CHANGE_NAME", playload: { index: 0, newName: "coderwhy" } }
那么我们如何将state和结合起来生成一个新的state从而达到修改state的目的呢?
使用函数,函数是一个纯函数。
const reducer = (state = initDataState, action) => {switch (action.type) {case 'INCREMENT':return { ...state, counter: state.counter++ }case 'DECREMENT':return { ...state, counter: state.counter-- }case 'ADD_NUM':return { ...state, counter: state.counter+action.num}case 'SUB_NUM':return { ...state, counter: state.counter-action.num }default:break;}return state
}
1.3 redux的三大原则
单一数据源
整个应用程序的state被存储在一颗 tree中,并且这个 tree只存储在一个 store 中
Redux并没有强制让我们不能创建多个Store,但是这样做并不利于数据的维护
单一的数据源可以使整个应用程序的state变得清晰、可维护
State状态是只读的
修改更新state唯一的方式就是的触发,不能再其他地方通过任何方式来对state进行修改。
使用纯函数来执行修改
通过将 旧state和 联系在一起,并且返回一个新的State:
2.redux的基本使用 2.1使用流程
安装redux:
yarn add redux
1.导入redux
const redux = require('redux');
2.创建一个对象,作为我们要保存的state
// 初始化数据
const initDataState = {counter: 0
}
3.创建函数
const reducer = (state = initDataState, action) => {return state
}
4.创建store来储存这个store(创建时必须使用作为参数)
const storeX = redux.createStore(reducer);
注:我们可以通过store.来获取当前的state值
5.通过来修改state,中通常都会有type属性,也可以携带其他的数据;
const actions1 = { type: 'INCREMENT' }
const actions2 = { type: 'DECREMENT' }
const actions3 = { type: 'ADD_NUM', num: 6 }
const actions4 = { type: 'SUB_NUM', num: 4 }
6.修改中的处理代码(这里一定要记住,是一个纯函数,不需要直接修改state)
const reducer = (state = initDataState, action) => {switch (action.type) {case 'INCREMENT':return { ...state, counter: state.counter++ }case 'DECREMENT':return { ...state, counter: state.counter-- }case 'ADD_NUM':return { ...state, counter: state.counter+action.num}case 'SUB_NUM':return { ...state, counter: state.counter-action.num }default:break;}return state
}
7.可以在派发之前,监听store的变化
storeX.subscribe(() => {console.log(storeX.getState());
})
8.派发
storeX.dispatch(actions1)
storeX.dispatch(actions2)
storeX.dispatch(actions3)
storeX.dispatch(actions4)
2.2 redux目录结构划分
如果我们将所有的逻辑代码写到一起,那么当redux变得复杂时代码就难以维护。
接下来,我会对代码进行拆分,将store、、、拆分成一个个文件。
我们在实际开发中一般会将redux部分划分为四个文件。
index.js文件,用于保存我们的store实例:
import redux from 'redux'
import reducer from './reducer.js';
export const store = redux.createStore(reducer);
.js文件(存放state和函数)
import { ADD_NUM, DECREMENT, INCREMENT, SUB_NUM } from "./constants.js"const initialState = {counter: 0
}export default (state = initialState, action) => {switch (action.type) {case INCREMENT:return { ...state, counter: state.counter+1 }case DECREMENT:return { ...state, counter: state.counter-1 }case ADD_NUM:return { ...state, counter: state.counter+action.num }case SUB_NUM:return { ...state, counter: state.counter-action.num }default:return state}
}
.js文件(所有的操作)
import { ADD_NUM, DECREMENT, INCREMENT, SUB_NUM } from "./constants.js";let increment = () =>
({type: INCREMENT
})let decrement = () =>
({type: DECREMENT
})
let add_num = (num) =>
({type: ADD_NUM,num
})
let sub_num = (num) =>
({type: SUB_NUM,num
})
export {increment,decrement, add_num, sub_num
}
.js文件(保存常量,统一处理type的值)
export const INCREMENT='INCREMENT'
export const DECREMENT='DECREMENT'
export const ADD_NUM='ADD_NUM'
export const SUB_NUM='SUB_NUM'
2.3 redux的处理流程
3.react结合redux
我们学习redux的最终目标就是在框架中使用它。在这一节我会先使用两种方式来引入这个模块的知识点,一个是在组件中直接使用redux,另一个是我们实现一个简单的的功能(利用高阶组件、)。其目的是为了是我们更好的理解一个库的使用——react-redux。
3.1 在组件中直接使用
我们可以模拟一个计数器的案例:
在组件中直接使用以下代码:
import React, { PureComponent } from 'react'
import { store } from '../store'
import { sub_num } from '../store/actionCreator'export default class About extends PureComponent {constructor(props) {super(props);this.state={counter:store.getState().counter }}render() {return (<div>About<button onClick={() => {this.btnclick(1)}}>-1</button></div>)}componentDidMount(){store.subscribe(() => {this.setState({counter:store.getState().counter})})}btnclick(num) {store.dispatch(sub_num(num))}
}
分析以下上面的代码,虽然达到了react与redux结合的目的,但是我们组件中的一些操作可能会出现一些重复,比如,在生命周期钩子中监听state数据变动的代码会出现重复;派发事件前都要先导入store的实例再调其对应的。那么我们能否将这些公共的部分提取出来呢?
3.2 自定义函数
首先我们需要创建一个函数
函数本身接收两个参数:
返回一个高阶组件:
import { PureComponent } from "react"
import StoreContext from "./ContentType";export default function connect(mapStateChange,dispatchChange) {return function handleMapCpn(WarppedComponnet) {class CpnComponent extends PureComponent{constructor(props,context) {super(props);this.state={//初始statestoreState:mapStateChange(context.getState())}}componentDidMount(){//订阅this.unsubscribe=this.context.subscribe(() => {this.setState({storeState:mapStateChange(this.context.getState())})})}componentWillUnmount(){//取消订阅this.unsubscribe();}render(){return <WarppedComponnet{...this.props}{...mapStateChange(this.context.getState())}{...dispatchChange(this.context.dispatch)}></WarppedComponnet>}}CpnComponent.contextType=StoreContext;return CpnComponent}
}
接着我们可以看一下使用:
其实通过我们定义的函数就可以得知在使用中我们需要分别传入两个存有state和的函数的映射,其作用是让返回我们redux实例中真正的state状态和,因为现在我们的组件时不会持有store的,这些是持有的,这也就是我们封装的意义。接着将我们当前的组件作为当前返回值的参数传递给内部的高阶组件处理函数。再将其高阶得到的高阶组件导出。
需要注意的是,我们当前就不再使用我们定义的组件,而是使用处理过后的高阶组件,因此我们就可以使用传给我们的props进行获取映射中的数据(也就是我们redux中的state值)及其。
栗子:
import React, { PureComponent } from 'react'
import { add_num } from '../store/actionCreator'
import connect from '../utils/connect'class About extends PureComponent {render() {return (<div><p>{this.props.counter}</p><button onClick={() => { this.props.addNumber(1) }}>+1</button><button onClick={() => { this.props.addNumber(5) }}>+5</button></div>)}
}
const mapStateToprops = (state) => ({counter: state.counter
})
const mapdispeathToprops = (dispatch) => ({addNumber(number) {console.log('');dispatch(add_num(number))}
})
export default connect(mapStateToprops, mapdispeathToprops)(About)
这个实例总有一个缺陷,就是需要我们自己导入store实例,那么这就导致我们封装的这个函数不够独立,假如别人要使用我们的这个工具类这就意味着别人要修改我们工具类的源码进行导入store。因此我们需要使用与来解决这个问题。
解决函数不够独立的问题
正确的做法是我们提供给使用者一个,用于创建我们的,让用户将store传入到value中即可
1.创建一个的文件:
import react from 'react'
let StoreContext=react.createContext();
export default StoreContext
2.修改函数中class组件部分的代码:
3.再组件的入口处将我们的组件根元素设置为.,接着再将store使用value导入。
import Home from './useourConnect/Home'
import StoreContext from './utils/ContentType'
export default class App2 extends PureComponent {render() {return (<StoreContext.Provider value={store}><Home></Home> <About></About></StoreContext.Provider>)}
}
代码实际上就是我们上面的附上的。
4.react-redux使用
虽然我们已经实现了、这些帮助我们完成连接redux、react的辅助工具,但是实际上redux官方帮助我们提供了 react-redux 的库,可以直接在项目中使用,并且实现的逻辑会更加的严谨和高效。
安装:
yarn add react-redux
使用方法实际上就是将我们之前,导入的函数替换成我们react-redux下的,其余的用法和我们自己封装的用法是一样的。
但是需要注意的是使用这一块有一些区别:
import React, { PureComponent } from 'react'
import { Provider } from 'react-redux'
import { store } from './store'
import About from './useReactRedux/About'
import Home from './useReactRedux/Home'export default class App3 extends PureComponent {render() {return (<Provider store={store}><About></About><Home></Home></Provider>)}
}
看到这里我们可以总结一下使用函数的好处:
5.中间件的使用 5.1.中间件作用和目的
在真实开发中,redux中保存的很多数据可能来自服务器,我们需要进行异步的请求,再将数据保存到redux中。
在之前学习网络请求的时候我们讲过,网络请求可以在class组件的中发送,所以我们可以有这样的结构:
(图片来自老师的公众号)
可是这样做有一些不妥:
也就是这样的流程:
(图片来自老师的公众号)
对于上面的问题就可以使用中间件来进行,首先关于中间件,学习过或Koa框架的童鞋对中间件的概念一定不陌生;在这类框架中,可以帮助我们在请求和响应之间嵌入一些操作的代码,比如解析、日志记录、文件压缩等操作;
redux也引入了中间件()的概念。
这个中间件的目的是在的和最终达到的之间,扩展一些自己的代码;比如日志记录、调用异步接口、添加代码调试功能等等;
5.2 redux-thunk的使用
我们现在要做的事情就是发送异步的网络请求,所以我们可以添加对应的中间件:
官方推荐的做法是网络请求的中间件是redux-thunk.
redux-thunk是如何做到让我们可以发送异步的请求呢?
我们知道,默认情况下的(),需要是一个的对象,redux-thunk可以让(函数),可以是一个函数,该函数会被调用,并且会传给这个函数一个函数和函数
1.首先第一步安装:
yarn add redux-thunk
2.在创建store时传入应用了的函数
import * as redux from 'redux'
import reducer from './reducer.js';
import thunkMiddleware from 'redux-thunk'
//通过applyMiddleware来结合多个Middleware, 返回一个enhancer;将enhancer作为第二个参数传入到createStore中;
const enhancer=redux.applyMiddleware(thunkMiddleware);
export const store = redux.createStore(reducer,enhancer);
3.定义返回一个函数的:
注意:这里不是返回一个对象了,而是一个函数;该函数在之后会被执行;
let getdata = (data) => {return {type: GETDATA,data}
}
const getHomeMultidataAction = () => {return (dispatch) => {axios.get("http://123.207.32.32:8000/home/multidata").then(res => {const data = res.data.data;dispatch(getdata(data.banner.list));})}
}
4.使用:
其实和我们正常使用的流程是一样的,首先在映射函数中添加对的引用,接着直接在需要派发的地方直接派发即可
componentDidMount() {this.props.getHomeMultidata();}function mapDispatchToProps(dispatch) {return {getHomeMultidata(){dispatch(getHomeMultidataAction())}};
}
这里有一个疑问,为什么要在里进行中间件的处理,为什么不直接在中处理请求?
因为是一个纯函数,如果在里面处理网络请求会使其变质
6.redux-
redux提供了类似于vue-的状态追踪工具,不过需要我们在代码中进行相关的设置才可以使用。
import { createStore, applyMiddleware, compose } from 'redux';
import thunkMiddleware from 'redux-thunk';
import reducer from './reducer.js';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
// 通过applyMiddleware来结合多个Middleware, 返回一个enhancer
const enhancer = composeEnhancers(applyMiddleware(thunkMiddleware,SagaMiddleware));
// 将enhancer作为第二个参数传入到createStore中
const store = createStore(reducer, enhancer);export default store;
就是要实现是个函数,如果有中间件直接将方法作为参数传入即可。
接着我们直接运行项目就直接可以使用这个插件来查看我们数据的状态了。
7.redux-saga
saga是一个类似于thunk的中间件,也是用于处理异步操作的中间件。不过使用的是的语法,将异步操作使用同步的方式来写。
如果你对的知识不是太了解,可以去看看阮一峰的教程
其实在我看来redux-thuck其实在派发时返回一个函数,当redux派发事件时会判断它的返回类型,如果是函数则直接调用函数,如果是对象则正常派发到;而saga不一样,saga拥有一个完整的数据环,它可以将直接拦截到自己的部分然后进行操作。
我们现在来看一下saga的具体使用:
1.安装:
yarn add redux-saga
2.在store/index中集成saga
import * as redux from 'redux'
import reducer from './reducer.js';
import thunkMiddleware from 'redux-thunk'
import createSagaMiddleware from 'redux-saga'
import saga from './saga'
// 创建saga中间件
const SagaMiddleware=createSagaMiddleware();
const store = redux.createStore(reducer,redux.applyMiddleware(thunkMiddleware,SagaMiddleware))
//不要忘记run
SagaMiddleware.run(saga);
export default store;
注意我们saga.js文件是我们即将具体处理拦截的地方
3.saga.js文件的编写:
import axios from 'axios';
import { put, takeEvery } from 'redux-saga/effects'
import { getdata } from './actionCreator';
import { FETCH_HOME_MUTIDATA } from './constants';function* fetchHomeMultidata(action) {const res = yield axios.get('http://123.207.32.32:8000/home/multidata');console.log(res);yield put(getdata(res.data.data.banner.list))
}
function* mySaga() {yield takeEvery(FETCH_HOME_MUTIDATA, fetchHomeMultidata)
}
export default mySaga;
关于redux的知识点我们讲到这里,你就可以在项目中使用了,需要注意的是,当项目中的state状态过于繁杂时我们需要考虑的拆分,这个点我们后续再来单独讨论。