首页 > 代码库 > 【前端,干货】react and redux教程学习实践(二)。
【前端,干货】react and redux教程学习实践(二)。
前言
这篇博文接 【前端】react and redux教程学习实践,浅显易懂的实践学习方法。 ,上一篇简略的做了一个redux的初级demo,今天深入的学习了一些新的、有用的,可以在生产项目中使用的前端架构,我将尽量以最简单的语言描述,如果有童鞋看不懂,也可以私下问我。
复习
前一节我们已经知道,一个redux应用,主要有几个概念,它们的共同作用都是管理一个全局state,使react组件的state集中处理,想一下你在写react组件的时候,组件的state总是或多或少与父级组件有关联,一般情况下我们都是用props处理的 —— 即在父级组件传入state,子组件使用props管理状态state。然而经过redux的处理,state被提升到最高等级的组件,它被具体化成一个对象,变得更加容易管理,可以直接在后端读取state改变前端交互。复习一下两个概念:
1.store
Store 就是保存状态数据对象的地方,你可以把它看成一个容器。整个应用只能有一个 Store。这个对象通过createStore创建,它的第一个参数传入一个函数,这个函数就是唯一能处理改变state的地方。
import { createStore } from ‘redux‘;const store = createStore(fn);
2、action
action是一个通知,例如我有一个组件,里面有一个click事件改变了当前的状态,那么就要通过这个click事件的回调,向store发出要更改state的action,store传入的那个函数接到通知以后,改变state,最后更新视图。发出action的方法是,一般action是一个对象,官方规定它必须有一个type属性,用来规定现在这个action是谁发出的。
store.dispatch(action);
好,就复习这两个概念,这几个流程可以概括为:
首先组件A通过回调发出action,然后reducers接受到这个action,处理完之后返回state对象更改,最后触发视图更新。一个redux交互的流程大概就是这样。
要看得懂下面的东西,你需要懂上一节写过的代码。
先实践
还是实践那个案例,一个number的加减:
目标是点击对应的按钮,数字加减,输入时值也随state变化。github地址:https://github.com/294678380/redux_demo (demo2)
先来看看上次我们是怎么发送action的:
我们通过直接在组件上挂props实现的向store发出action
const render = ()=> ReactDOM.render( <div> <Number value={store.getState()} add={(action)=>store.dispatch(action)} //传入一个函数,传入发送过来的action,由reducers处理之后返回state, less={(action)=>store.dispatch(action)} /> </div>, content);
class Number extends Component{ add(){ this.props.add({type:"NUMBER_ADD"}) //调用app.js中传过来的箭头函数,传入了type为NUMBER_ADD的action }
//.....省略
}
通过传过来的add箭头函数,发出了type是NUMBER_ADD的action,然后reducers接受到了这个action,更改了state引发视图更新。
现在我们要改造这份代码
为什么这样的方式有问题?
现在这样的代码能看到的至少有三点问题:
1.发出的那个action是组件内确定的,结果就是,我需要在组件内写一个type,比如那个“NUMBER_ADD”,在reducers处理函数中还要再判断type等于“NUMBER_ADD”,如果需要更改,增加了应用耦合。
2.监听是全局的,也就是说每次更新都是全局的。这影响效率。
3.代码也不够优雅,要填写很多重复的props。
让我们来看代码:
app.js
import React from "react";import ReactDOM from "react-dom";import {InitReducers} from "./reducers";import {createStore} from "redux";//这个是react-redux提供的管理state的组件,把它放在最高层就可以实现统一管理state。import {Provider} from "react-redux";import Number from "./number";//createStore生成store,InitReducers在收到action时调用的函数const store = createStore(InitReducers);const content = document.querySelector(".content");//直接renderReactDOM.render( <Provider store={store}> <Number /> </Provider>, content);
入口做了更改吧监听函数store.subscribe()移除,使用Provider包裹的方式发布state,注意 createStore(InitReducers); 传入的这个函数是从reducers.js导出的InitReducers函数。
现在我们看reduces.js
import * as Actions from "./actions"; const ReducersActions = { [Actions.NUMBER_ADD]:(state,action)=>{ state.number++; return state; }, [Actions.NUMBER_LESS]:(state,action)=>{ state.number--; return state; }, [Actions.NUMBER_VALUE]:(state,action)=>{ state.number = action.value; return state; }}function InitReducers(state={},action){ state.number == undefined?state.number=0:state.number; if(action.type === "@@redux/INIT"){ return state; } state = ReducersActions[action.type](state,action); return Object.assign({},state);}export {InitReducers}
引入了actions.js文件,这里就是要解决前面说的,action需要一个统一入口的问题。这个actions.js定义了所有action.type,函数 InitReducers被触发时,ReducersActions[action.type](state,action);会被调用,这个action.type是组件发过来的action。
现在我们看actions.js
export const NUMBER_ADD = "NUMBER_ADD";export const NUMBER_LESS = "NUMBER_LESS";export const NUMBER_VALUE = http://www.mamicode.com/"NUMBER_VALUE";export function setNumber(type,value=false){ switch(type){ case "add": return {type:NUMBER_ADD} case "less": return {type:NUMBER_LESS} case "value": return {type:NUMBER_VALUE,value:value} }}
我在这里定义了三个action,
NUMBER_ADD 增加
NUMBER_LESS 减少
NUMBER_VALUE 输入
一个函数
setNumber 这个函数是给number组件调用的。
然后number.js
import React,{Component} from "react";import {connect} from "react-redux";//bindActionCreatorsimport {bindActionCreators} from "redux";//引入actionsimport * as Actions from "./actions";const propTypes = { actions:React.PropTypes.object.isRequired, number:React.PropTypes.number.isRequired}class Number extends Component{ setNumber(event){ let {target} = event; let type = target.getAttribute("data-type"); if(type != "value"){ this.props.actions.setNumber(type); }else{ this.props.actions.setNumber(type,target.value); } } render(){ let props = this.props, setNumber = this.setNumber.bind(this); return <div> <input type="text" onChange={setNumber} data-type="value" value=http://www.mamicode.com/{props.number}/>"color: #800000;">"add" onClick={setNumber}>+</button> <button data-type="less" onClick={setNumber}>-</button> </div> }}Number.propTypes = propTypes;//定义组件内需要使用的state某个值function mapStateToProps(state){ return { number:state.number }}//定义组件内需要调用回调改变state的props参数function mapDispatchToProps(dispatch){ return { //这就是合并actions.js中定义的函数,在组件内this.props.actions.xxx调用bindActionCreators生成的dispatch函数 actions:bindActionCreators(Actions,dispatch) }}export default connect(mapStateToProps,mapDispatchToProps)(Number);
先把视线转到button的click:
调用了setNumber方法
setNumber调用了 this.props.actions.setNumber(type,target.value); 传入type,如果是输入则传入value,这里调用的就是actions.js中export出来的setNumber函数,那么它是怎么传进来的呢?
//定义组件内需要使用的state某个值function mapStateToProps(state){ return { number:state.number }}//定义组件内需要调用回调改变state的props参数function mapDispatchToProps(dispatch){ return { //这就是合并actions.js中定义的函数,在组件内this.props.actions.xxx调用bindActionCreators生成的dispatch函数 actions:bindActionCreators(Actions,dispatch) }}export default connect(mapStateToProps,mapDispatchToProps)(Number);
就是这一段,
第一个函数,mapStateToProps 这个是定义组件内需要获取的,state的值,就是你全局的状态会被传入这个函数,你需要指定哪一个属性你需要放到组件的props里面。
第二个函数,mapDispatchToProps 这个是定义组件内需要获取的,能调用到 dispatch函数的回调。它也是放到props里面,和第一个state的区别是,这里放进去的是函数,是能够调用actions.js里定义的函数。
这里的
return { //这就是合并actions.js中定义的函数,在组件内this.props.actions.xxx调用bindActionCreators生成的dispatch函数 actions:bindActionCreators(Actions,dispatch) }
这个actions就是那句 this.props.actions对象,它的值是 bindActionCreators(Actions,dispatch)。这个函数把actions.js中所有导出的函数转换成能够触发dispatch的函数,在组件里调用this.props.actions.setNumber时,实际上在setNumber处理完action之后,又会自动调用store.dispatch这个方法发出action。
然后总结要点
要点一:Provider组件包裹整个应用,这样就能通过这一个超级父组件管理所有子组件的state。
要点二:bindActionCreators函数,绑定action,创建dispatch。返回一个actions对象,然后传入组件中,最后组件调用actions里面定义的方法,方法处理action调用store.dispatch,这是整个架构的关键点
看一点别人的项目实践
最近在看一个项目:superset。 这是一个大数据可视化集成软件,它的前端架构是基于react+redux的,里面的整体架构,可以看到如图:
app.jsx:
看看它的做法,它将actions对象分发到需要全局state的所有组件中,然后在这些组件中就不需要定义任何与redux相关的代码了:
最后总结
打字到这里,手有点酸,先不总结啦。
demo代码github:https://github.com/294678380/redux_demo
=============================
转载需在明显处注明作者与出处链接。
【前端,干货】react and redux教程学习实践(二)。