5 React项目架构 - Store
Sun, Sep 23, 2018
什么是Store
store - 数据流解决方案
- mvc模式在前端并不特别适用:
- 很多数据都是异步的
- 页面之间共享一部分数据,数据更新的时候需要让view层检测到数据的更新
- 否则会导致view层和model层脱节,渲染出错
- store:「flux单向数据流模式」中,整个app有个单独存放数据的地方,叫store
- 页面上的所有内容都是根据store上的内容渲染出来的
- store里的任何数据的变化,都会影响到view的渲染效果
- store决定了页面展示成什么样子
- 解决mvc中数据维护问题,把数据维护集中在一个地方
- 只要使用action处理数据,view就能自动更新
- react:是一个视图层解决方案
- 它的虚拟dom特性,让我们即便把整个数据对象都彻底更新了(换了个数据树object),react也能高效处理视图重新渲染过程
- 虚拟dom的出现,让这种单一数据流模式得以实现
Mobx - 一种flux模式下,数据流解决方案的后起之秀
- 文档
- 安装:`npm i mobx mobx-react -S`
- 它的所有数据一旦更改,绑定数据的地方会立刻更新
- 使用方法比Redux简单很多
- Redux
- 涉及Action、Dispatcher、Reducer、Store,概念比较多,学习成本较高
- 数据一旦更新,需要做整棵数据树的全拷贝,生成一棵新树,浪费资源
- 每发一个action都会产生一个新对象
- 新对象的到来,会触发所有Component的重新渲染
- 由于虚拟dom,这种重新渲染成本不高,所以可以这么做,效率高
- vue没有虚拟dom机制,所以无法这么做
- Mobx
- 声明Store的时候,声明其为Observable就好
- 值改变会自动通知对应view,触发该view刷新
- 整个数据都只有一份,不会生成新的一个对象
- 只不过它有数据变动,会通知对应的view
- 执行效率高一些,不需要每次都拷贝一个新的数据树
- Redux
代码实现
.babelrc
{ "presets": [ "stage-1", ... ], "plugins": [ "transform-decorators-legacy", ... ] } |
说明:
- babel编译配置,需要加上stage-1,因为他的语法不是es6的标准语法
- 需要装一个babel的插件,且需要放在第一项,否则会有问题
- 安装
- `npm i babel-plugin-transform-decorators-legacy babel-preset-stage-1 -D`
- 验证:
- 重启dev:client看下能否编译通过
测试mobx
client/store/app-state.js
import { observable, computed, action, autorun, } from "mobx" class AppState { @observable count = 0 @observable name = 'Azen' // #2 @computed get msg() { // #3 return `${this.name} say count is ${this.count}` // #4 } @action add() { // #7 this.count += 1 } @action changeName(name) { this.name = name } } const appState = new AppState() autorun(() => { // #5 console.log(appState.msg) }) setInterval(() => { // #6 appState.add() }) export { appState as default, AppState } // #8 |
说明:
- 使用computed等Mobx相关特性的时候,使用class去组织最方便,可以通过this调用相关功能
- 使用装饰器指定值
- 计算属性,可以使用点语法调用
- 使用模板语句拼句子
- autorun:文档mobx内置方法,当AppState更新,会自动调用
- setInterval:js内置方法,每秒调用一次,用于测试
- 声明action:需要保证只有action能更新state
- AppState在propTypes里会用到,也需要导出下
app.js
import { Provider } from 'mobx-react' import appState from './store/app-state' const render = (Component) => { ReactDOM.hydrate( <AppContainer> <Provider appState={appState}> // #1 <BrowserRouter> <Component /> </BrowserRouter> </Provider> </AppContainer>, root, ) }; |
说明:
- 需要用Provider给入口包一层,把定义好的appState传给它
- 包一层:
- 原理是使用了隐藏的react api,名字叫context
- 让内容从顶层传到下面任何一层组件,任何一层都可以拿到
- 包一层:
使用Store
client/views/topic-list/index.jsx
import React from 'react' import { observer, inject, } from 'mobx-react' import PropTypes from 'prop-types' // #2 import { AppState } from '../../store/app-state' @inject('appState') @observer // #1 class TopicList extends React.Component { constructor() { super() this.changeName = this.changeName.bind(this) // #4 } componentDidMount() { // placeholder } changeName(event) { this.props.appState.changeName(event.target.value) } render() { return ( <div> <div>ㄟ...这里是to...topic li..li..list(。•́-ก̀。)</div> <br /> <input type="text" onChange={this.changeName} /> <span>{this.props.appState.msg}</span> </div> ) } } TopicList.propTypes = { // appState: PropTypes.object.isRequired // #3 appState: PropTypes.instanceOf(AppState).isRequired, } export default TopicList |
说明:
- @inject:app.js中包的Provider提供的属性名字
- @observer:声明这个组件是observable的
- 声明props类型
- 安装`npm i prop-types -S`
- airbnb的eslint规则,不让把类型设置为objecte类型,因为js里所有东西都是object
- 需要绑定的原因:在我们执行onChange的时候,上下文已经不在组件内部了,但是我们需要用到this,所以就通过bind的方式,让执行changeName的时候可以调用this