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