WdBly Blog

懂事、有趣、保持理智

WdBly Blog

懂事、有趣、保持理智

周维 | Jim

603927378@qq.com

react项目优化

移除项目中依赖的redux

需求

bi项目做为内部报表处理系统,拥有了并不属于这个规模系统所有的复杂度,这主要是项目中的redux的滥用,我们这次的工作主要是对项目的redux进行优化,同时调整项目的目录。

优化前项目的结构和流程

原项目以每张报表为单位将项目拆分:

containers: 项目的容器组件

容器组件是拥有状态的组件,redux通过connect(store)(UI component)将一个UI组件和store(state, action)连接成为容器组件。

connect( state => ({ routing: state.routing, comment: state.comment, origin: pathname }), dispatch => ({ actions: bindActionCreators({ download, initComment }, dispatch) }) )(OrderSalesReport)

bindActionCreators :将dispatch传入actions中

//不使用 bindActionCreators connect( state => ({ routing: state.routing, comment: state.comment, origin: pathname }), dispatch => ({ download: () => { dispatch({ type: 'DOWNLOAD', ... }); }, ... }) )(OrderSalesReport) //使用 bindActionCreators 可以在download中使用dispatch download() { return dispatch => { dispatch({ type: 'DOWNLOAD', ... }); } } //或者直接返回一个对象 会自动dispatch download() { return { type: 'DOWNLOAD', ... } }

actions: actions处理函数对应于每个容器组件

reducers: actions中发出的dispatch会被对应的reducers接收到

reducers在收到一个dispatch后会找到对应的origin下的type,执行操作,返回一个对象,容器组件会自动更新(setState的自动调用)视图。

由于我们的 actions 和 reducer中已经耦合了太多的业务代码,所以我们需要想个折中的办法来处理问题。

分析:在容器组件中发起action

//1 containers componentDidMount() { this.props.actions.fetchList() } //2 actions export function fetchList() { return (dispatch, getState) => { //async get data ... dispatch({ origin: 'ORDER_VISA_SALES_REPORT', type: 'SETLIST', list }); }; };

看到这我们可能想到在action中不去dispatch 而是直接将list返回给containers,然后手动通过setState更新视图,像这样:

//2 actions export function fetchList() { return (dispatch, getState) => { //async get data ... return { origin: 'ORDER_VISA_SALES_REPORT', type: 'SETLIST', list }; }; };

这时我们注意到外层有个通过bindActionCreators包的一层函数,所以我们的return不能直接被调用点收到

//1 containers componentDidMount() { this.props.actions.fetchList() //undefined }

这时我们把action也干掉,改成这样

import { fetchList} from './actions'; //1 containers componentDidMount() { var list = fetchList(); }

但这时有个问题,action中数据是异步获取的,上方这种获取不太合理,且有这种情况需要考虑:

//2 actions //有多个dispath的问题 export function fetchList() { return (dispatch, getState) => { dispatch({ origin: 'CHANGELOADING', type: 'CHANGELOADING', loading: true }); //async get data ... dispatch({ origin: 'ORDER_VISA_SALES_REPORT', type: 'SETLIST', list }); }; };

处理:使用callBack处理

import { fetchList} from './actions'; //3 callBack callBack(res){ this.setState(res); } //1 containers componentDidMount() { fetchList(this.callBack) } //2 actions export function fetchList(callback) { callback({ origin: 'CHANGELOADING', type: 'CHANGELOADING', loading: true }); //async get data ... callback({ origin: 'ORDER_VISA_SALES_REPORT', type: 'SETLIST', list }); };

这个时候我们已经解决了dispatch的问题,就算一个action有多个dispath我们同样可以对应的修改视图,但是有个问题,我们以前通过dispath会通知reducers处理不同的业务逻辑,如果我们在这里要去改动这块,无疑是令人难受的。于是我打开reducers看了下,咦:

//reducers import {CHANGE_LOADING, FETCH_LIST} from './types'; const initialState = { loading: false, list: [], ... } export function orderSalesReport(state=deepCopy(initialState), action) { switch(action.type){ case CHANGE_LOADING: return loading case FETCH_LIST: return list } }

这个reducers其实就是接收一个state,和action做为参数,最后返回一个新的state嘛,于是我灵机一动:

//containers import { orderVisaSalesReport } from './reducers' constructor(){ this.state = { //获取初始值 state:orderVisaSalesReport(undefined, {}) } } callBack(res){ this.state.state = orderVisaSalesReport(this.state.state, res) this.setState({ state:this.state.state }); }

这样我们就可以完全不用动原来的业务代码了。当然此处有一坑。

//containers callBack(res){ //这是老的写法 this.setState({ state:orderVisaSalesReport(this.state.state, res) }); } componentDidMount() { fetchList(this.callBack) } //actions export function getOther(callback) { callback({ origin: 'CHANGELOADING', type: 'CHANGELOADING', loading: true }); //连续两次的callback 会导致数据丢失 callback({ origin: 'CHANGELOADING', type: 'CHANGELOADING', loading: true }); };

这时确定下来一个修改方案:流程如下

1:修改containers

//从上往下的顺序 //1 引入对应的reducers import { orderVisaSalesReport } from './data-handle-visa' //2 添加state this.state = { state: orderVisaSalesReport(undefined, {}) } //3 添加回调函数 callback callBack(res){ this.state.state = orderVisaSalesReport(this.state.state, res) this.setState({ state:this.state.state }); } //4 搜索this.props 删除this.props.action.的调用 //this.props.action.fetchList() 改成直接调用 fetchList() //5 为action函数添加回调 fetchList(this.callBack) //6 修改页面数据来源 //未修改时 const {data1, data2} = this.props.data; //修改为 const {data1, data2} = this.state.state; //7 删除connect改成直接返回 export default OrderSalesReport //action //1 为函数添加 callback和state参数 export function fetchList(callback, state){} //2 将dispatch 替换为 callback //循环上面的操作 直到所有报表全部处理

项目结构变动

同样是以报表为单位,将与这张报表相关的文件集合起来,将多个组将共用的文件拆分:

对一个报表文件夹:内部应该有 入口文件(也就是以前的容器组件文件),action处理文件,reducers文件,types文件,表单文件(form.js,list.js),相关的样式文件(xxx.less)。
image.png

以后若是需要新增报表同样 只需新增一个文件夹下这些文件即可,当然不是必须的,你可以不用这样一个模式是允许的。

同样如果需要修改报表,也只需修改该文件夹下的内容即可。

以前的redux中的状态树的数据是以全局变量的什么存在于内存中的,但是这些数据其实并不是每个页面都要使用,造成浪费。