前言
最近由于公司需求,想要快速开发一个后台管理系统,上头看上了飞冰能够快速搭建UI界面,但是基于react开发的,本人之前是用react+mobx开发了个后台管理系统,但是发现主流的还是react+redux+saga的组合拳。刚好上面把这个项目给我来写了,于是我决定硬着头皮一边自学redux和saga尝试,当时每天早上坐公交的路上不断看redux和saga的知识,去看了GitHub的几个react完整项目后,直接用这套主流的组合。
飞冰是什么?
首先介绍下飞冰,飞冰是一套综合解决方案,用来极速构建中后台应用,飞冰是个有一定超越,又不重合 Ant.Design 的概念。后者是一个工具箱,而前者是一个成型的产品模板。如图:
可以自由选择相对应模板,然后组装。这样的话,在UI和界面上会省事很多。具体怎么玩我就不介绍了,官网下载个,自己尝试下,还是挺简单的,非常的组件化和可视化。
项目结构与数据传递
如图,飞冰构建下来的项目很组件化,我个人喜欢的是,把(redux)数据传递写在父组件页面上,然后那个子组件需要什么数据,我只给它传它需要的数据,如果适合的话,可以让子组件是一个无状态函数式组件。
关于无状态函数式组件
引自Levid_GC的翻译文章【译】在 React 中拥抱函数——无状态函数式组件及其重要性。
- 特点
无状态函数式组件的特点就是没有this和ref和无生命周期方法。
函数式组件,有时也被称为无状态组件,没有任何生命周期方法,意味着每次上层组件树状态发生变更时它们都会重新渲染,这就是因为缺少 shouldComponentUpdate 方法导致的。这也同样意味着您不能定义某些基于组件挂载和卸载的行为。
有个误区就是说:简单地认为使用纯无状态函数式组件可以获得性能上的提升。这个观点是不正确的。相反,当我们需要处理大量无状态函数型组件的时候,它的对立观点却是正确的。
项目子组件实例:
const TodoListUI = (props)=> {
return (
<div>
<div>
<Input placeholder="Basic usage"
value={props.inputValue}
style={{width:'300px', margin: '10px',}}
onChange={props.handleInputChange}
/>
<Button type="primary" onClick={props.handleBtnclick}>提交</Button>
<List
style= {{margin: '10px',width:'300px'}}
bordered
dataSource={props.list}
renderItem={(item,index) => (<List.Item
onClick={props.handleItemDel.bind(this,index)}>{item}</List.Item>)}
/>
</div>
</div>
)
}
//普通组件,是类,有生命周期,比无状态函数组件性能损耗高些,若只有render函数(只负责渲染),即可用无状态组件。
- 那为何要用无状态函数组件?
使用无状态函数式组件最大的好处就是它能够将容器型和展示型组件明确区分开来,避免产生大型以及杂乱的组件。
来自oNexiaoyao对上面的翻译文章的评论,个人觉得挺不错的,可以作为一个参考:
我倒觉得官方推荐使用无状态组件更多的是鼓励我们采用组件化的思想,将原来的一个比较大的展示组件逐步拆分成一个一个小组件,然后再将小组件拆分成更小的组件,最后大部分组件都是可以拆成很多个小的无状态的组件(只提供页面层的展示),将控制与展示分离,逻辑层次结构更加的清晰。但是这样就会带来一个问题:代码拆到最后是不是会显得比较繁琐呢?。当然,这样的组件化的思想一旦形成,对于代码结构的组成或者说代码的复用能力都是一种极大的提升。
当然,用不用都是看实际项目情况,如果想让你手上的代码做个“精致的猪猪男孩”,可以考虑。
页面结构与数据传递
贴一个我的某个父组件页面的代码
import React, { Component } from 'react';
import BasicTab from './components/BasicTab';
import InfoDisplayTable from './components/InfoDisplayTable';
import UserTable from './components/UserTable';
//以下引入所有关于redux
import { connect } from 'react-redux';
import { compose } from 'redux';
import { bindActionCreators } from 'redux';
import injectReducer from '../../../utils/injectReducer';
import reducer from '../../../redux/Merchant/reducer';
import * as merchantAction from '../../../redux/Merchant/action';
class UserDetail extends Component {
constructor(props) {
super(props);
this.state = {};
}
componentDidMount(){
//...省去部分不相关代码
const {actions} = this.props
actions.userResultDetail({
'我是请求要的key':'我是请求要的value'
})
}
fndelete =(uuid)=>{
//...省去部分不相关代码
}
fnupdate=(obj)=>{
//...省去部分不相关代码
}
fnaccount=(obj)=>{
//...省去部分不相关代码
}
render() {
return (
<div className="user-detail-page">
{/* 可筛选过滤的用户类表格 */}
<UserTable sn = {this.props.state.user} //'这里只传子组件需要的数据和事件'
fndelete = {(id)=>this.fndelete(id)}
fnupdate = {(obj)=>this.fnupdate(obj)}
fnaccount = {(obj)=>this.fnaccount(obj)}
/>
{/* 基础 Tab 组件 */}
<BasicTab data = {this.props.state.list} /> //'这里只传子组件需要的数据'
</div>
);
}
}
const mapStateToProps = (state) => {
return { state:state.merchant.detail };
};
const mapDispatchToProps = (dispatch) => ({
actions: bindActionCreators(merchantAction, dispatch)
});
const withConnect = connect(
mapStateToProps,
mapDispatchToProps
);
const withReducer = injectReducer({ key: 'merchant', reducer });
export default compose(
withReducer,
withConnect
)(UserDetail);
这是一个完整的父组件页面,个人喜欢父组件进行数据的请求触发和处理,子组件就乖乖的拿数据负责渲染就好了,所以我也推荐可以考虑用无状态函数组件适当提高下性能。
这样的明确分工会让我写的比较舒服,一旦有关于数据的问题,我会在父组件上先寻找原因,而且这样只需要引入一次redux就可以了。 写的多了,你一看后台管理,自动会把这页面分成一块一块的。
页面大概是这样,飞冰自建下来的项目结构还是比较清晰的,接下来来看下我们的数据,是怎么流动的!
单向数据流Redux
两张图很直观体现了redux的好处。
使用前:
单向数据流的统一性和可预测性都大大提高了开发的方便和效率。
关于redux的学习,个人推荐这个GitHub入门完全理解 redux。
redux的教程琳琅满目,当时我也看了好几个,刚开始入门觉得好复杂啊,各种api和概念整得我一脸懵逼,跟vuex比起来,好像难了很多。 经过一轮的学习,我觉得其实只要知道redux的原理和核心思想,其它的,都只是辅助罢了。
redux的核心便是:单向数据流。
有个故事是这样的:
有个叫button的带领了一群战士侵占了一个store国的边境的土地,特意放走了个俘虏,还让他带话说:“winter is coming”。
俘虏连忙跑到Store的首都找到了Store的国王,国王知道了此事,开了个紧急会议,问军师Action应该怎么办。
Action听到了winter is coming这句话的时候,颤抖了下,并说:“他们真的来了,我们要认真对待这件事了,我建议排上我们最好的骑士Reducer带领三千精兵去夺回我们的边境。”
于是传来Reducer,这个Reducer人狠话不多,上到会议当听到winter is coming时候,只说了两个词:“where和how! ”
话音未落,reducer则带上兵,走上讨伐之路。
过了不久,Store国便传来Reducer讨伐成功的喜讯,收复边境。
这个故事转换成redux,是这样的:
在store的环境下,button触发了事件,并且dispatch(带话),action其实就是一个会文不会武(一个对象,仅此而已)的军师,当它收到那句话 的时候,它就明白是什么情况,于是让相对应的reducer去处理,reducer也是只接收两个参数,根据state提供位置来修改,根据action的type来进行相对应的sate修改操作。
Store
Store,唯一的数据管理者,贯穿整个应用。
Store是Redux中数据的统一存储,维护着state的所有内容,所以Store的主要功能就是:
-
维护应用的state内容
-
提供getState()方法获取 state
-
提供dispatch(action)方法更新 state
-
提供subscribe(listener)方法注册监听器
看到Store提供的方法,就可以把Action、Reducer和Store联系在一起了:
Store通过dispatch(action)方法来接收不同的Action, 根据Action对象的type和数据信息,Store对象可以通过Reducer函数来更新state的内容。
Action
Action是一个对象,用来代表所有会引起状态(state)变化的行为。 只描述行为信息,(一个列表,按照我的表单来干事)。
必须包含type这个属性,reducer将根据这个属性值来对store进行相应的处理。除此之外的属性,就是进行这个操作需要的数据。
假如我们要实现一个任务管理系统,那么添加任务的Action对象就会是下面的形式:
{
type: 'ADD_TASK',
name: 'Read ES6 spec',
category: 'Reading'
}
Reducer
Action对象仅仅是描述了行为的相关信息,至于如何通过特定的行为来更新state,就需要看看Reducer了。 是个函数。
接受两个参数:要修改的数据(state) 和 action对象。根据action.type来决定采用的操作,对state进行修改,最后返回新的state
最简单的描述就是: Reducer是一个函数 该函数接收两个参数,一个旧的状态previousState和一个Action对象 返回一个新的状态newState
以上,是redux的核心内容,如果你知道它的原理是如此,接下来的只有那些api的改变了,万变不离其宗。
小结
以上,介绍了飞冰是什么和个人项目的页面结构,还有redux的简单描述。 本人也是在学习中,若有大神发现错误,还请多多指教。
过了一段时间,button又再一次入侵store国,这次不同的是,它带来了更强大的队伍………………