- 文中的蓝色字体是相关内容的超链接,网址不另外列出,请放心点击。
- 本文内容适合 MobX 和 React 新手,也欢迎 MobX 和 React 专家指导点评。
摘要
阅读本文并实际上手编码运行,你将解决如下几个疑问:
- MobX如何进行状态管理
- MobX如何管理异步操作对状态的改变(fetch的使用)
- 如何创建一个简单的MobX+React 示例应用(无路由)
工具
- JetBrains WebStorm
- Node.js
预备知识
- 熟悉 ES6 相关知识
- 了解 React 相关知识
- 会使用 create-react-app 脚手架创建一个 react 应用
MobX API
在开始搭建我们的第一个 MobX+React 应用前,首先需要大致地认识下 MobX 的 API ,了解 MobX 的核心概念,明白 MobX 的工作流程以及 常见陷阱 。有了相关知识储备后再进行开发,往往能使我们编码更加得心应手,少走弯路,不用再劳心劳力和 bug 斗智斗勇。故还请初次接触 MobX 的读者仔细阅读 API 文档。
请务必熟悉以下标签的概念和作用
- @observable / observable()
- @observer / observer()
- @action / action()
- @computed
- @inject
示例应用需求以及效果展示
示例应用需求
本示例应用需求是实现通过输入股票代码查询到相关股票信息并展示出来的功能。关于获取股票相关信息,则通过新浪财经的证券股票数据接口进行获取。由于该接口并未实现 cors 跨域资源共享标准 ,会存在跨域访问的问题,所以我们在 自己编写的后端项目中 获取该接口返回的数据并实现cors跨域资源共享标准后传递给前端示例应用。(这只是一种跨域问题的解决方案,如果读者有其他跨域问题的解决方案请自行修改实现。)
本示例应用为了简单起见,并未添加相关css样式文件,如读者有兴趣,可自行添加。
效果展示
启动应用后界面如下(就是辣么粗犷……)
点击查询按钮后如下所示(依旧辣么粗犷甚至有点不羁……)
第一步:使用 create-react-app 脚手架创建一个React应用
MobX采用的是ES7的装饰器语法,目前还是一种实验性的语法,使用 create-react-app 脚手架默认创建的项目是没有开启装饰器语法的,故使用 custom-react-scripts 这种方式来创建项目。
命令行内输入npx create-react-app my-app --scripts-version custom-react-scripts
创建项目。
其创建的项目根目录路径下有一个拓展名为 .env 的文件,这个文件中定义了 custom-react-scripts 为项目新增的特性。
打开该文件可以看到REACT_APP_DECORATORS = true;
表示启用了装饰器语法。
第二步:安装相关依赖
查看项目目录下的 package.json 文件,此时仅安装了 react
和react-dom
依赖。
我们需要手动安装mobx
和mobx-react
依赖,以及 MobX 开发调试工具。
在终端命令行切入到我们的项目目录:
-
cd my-app
在终端命令行输入以下命令进行安装: npm install mobx
npm install mobx-react
npm install mobx-react-devtools --save-dev
安装完相关依赖后我们就可以正式进入第一个入门实例项目的编写了。
第三步:构造项目目录
我们可以构造如下所示的项目目录:
根目录
|--src #开发文件目录
| |---components # react 组件目录
| | |--index.js # 组件文件
| |--models # 领域模型目录
| | |--StockModel.js # 领域state文件
| |--stores # 保存state的Store目录
| | |--index.js # 根Store目录
| | |--StockStore.js # 领域Store目录
| |--index.js
MobX 中的 state 一般会封装在不同的 store 中,store 不仅保存了 state ,还保存了操作 state 的方法。对于与领域直接相关的 state ,一般会创建专门的 model 实体类,用于描述 state 。
第四步:设计 store 和 state
store 的职责是将组件使用的业务逻辑和状态封装到单独的模块,这样组件就可以专注于UI渲染。
首先设计我们的model实体类StockModel,用于描述股票信息的state。
股票信息接口返回的是是一个字符串,我们决定在领域store中把它的数据解析出来并保存在数组里,所以在实体类中我们决定使用了一个fromArray方法来创建我们的StockModel实体。
//领域state
import {observable} from "mobx";
class StockModel {
store;//领域state所属的领域store
@observable code;//股票代码
@observable stockName;//股票名称
@observable tPrice;//今日开盘价
@observable yPrice;//昨日收盘价
@observable nPrice;//今日当前价格
@observable hPrice;//今日最高价
@observable lPrice;//今日最低价
constructor(store,code,stockName,tPrice,yPrice,nPrice,hPrice,lPrice){
this.store = store;
this.code = code;
this.stockName = stockName;
this.tPrice = tPrice;
this.yPrice = yPrice;
this.nPrice = nPrice;
this.hPrice = hPrice;
this.lPrice = lPrice;
}
static fromArray(store,code,arr){
return new StockModel(
store,
code,
arr[0],
arr[1],
arr[2],
arr[3],
arr[4],
arr[5],
arr[6]);
}
}
export default StockModel;
设计完了具有相关领域 state 的实体类,我们需要创建一个保存state和相关操作 state 的领域 Store 。
在该领域Store内我们定义了一个 state [stocks
] 用以保存将要从服务获取到的股票信息实体。我们还定义了一个动作 [fetchStockByCode
] 用于从后端获取股票信息。需要特别注意的是,fetch是一个异步操作,所以需要编写 异步action 来进行对state的操作。这里我们采用action
关键字来包装promises
回调函数。即在获取到数据后再发送一个action
操作 state [stocks
] 的变更。
//领域state
import { observable, action,} from "mobx";
import StockModel from "../models/StockModel";
class StockStore{
@observable stocks=[]; //数组元素是PostModel的实例
//从服务器获取股票信息
@action fetchStockByCode(code){
//跨域访问
const headers = new Headers({
"Access-Control-Allow-Origin":"*"
});
return fetch('http://127.0.0.1:8080/myapp/api/getStockInfo?code='+code,{method:"GET",headers:headers,mode:"cors"})
.then(function (response){
return response.text();
})
.then(
action(
data =>{
const info = data.match(/".+"/)[0];
const target = info.replace(/"/g,"");
const item = target.split(","); //目标信息数组
this.stocks.clear();
this.stocks.push(StockModel.fromArray(this,code,item));
return Promise.resolve();
}
)
)
}
}
export default StockStore;
每一个应用中不能初始化多个相同的领域 Store ,除非你想使得你的应用中的state变得相当混乱。
我们可以创建一个根 Store,来管理和初始化我们的各个领域 Store 或其他的 Store 比如应用状态 Store 、UIStore 等。(为了使我们这个示例应用更加简洁明了,故我们只有一个领域Store,即StockStore
)。
//根Store
import StockStore from "./StockStore";
const stockStore = new StockStore();
const stores = {stockStore,};
export default stores;
第五步:绘制视图层
store 和 state 设计好了自然要开始设计我们的展示的视图层了。
在视图层,首先要明晰我们的交互逻辑,输入股票代码,触发拉取股票信息的动作,获取到股票信息后触发更新StockStore
中保存的 state [ stocks
] 的状态,从而自动触发 Computed value 对 state 变更的响应 获取到最新的股票信息数据,接着再自动触发 Reactions 对 state 变更的响应 [ 即组件内render()方法 ] 使得UI重新渲染。
为了使得渲染更有效率,我们最好尽量地使用小组件。
此时也要特别注意一些使用MobX的陷阱,比如从 observable 属性中提取数据并存储,这样的数据是不会被追踪的。
所有使用到@observable
的组件都要加上@observer
。别担心,@observer
越多,渲染效率越高。
@inject
将组件需要用到的具体store从根store中注入进来,具体理解需要结合下一步查看。
inject 是一个高阶组件( 注意:高阶组件不是React组件而是个函数 ),它和 Provider 结合使用,用于从 Provider 提供的 state 中选取所需数据,作为 props 传递给目标组件。
import React,{ Component } from 'react';
import { observable, action, computed } from "mobx";
import { inject, observer } from "mobx-react";
@inject("stockStore")
@observer
class StockPage extends Component{
render(){
if(this.props.stockStore.stocks.length ===0 ){
return (
<StockInput/>
);
}
return(
<div>
<StockInput/>
<StockInfoView />
</div>
);
}
}
@inject("stockStore")
@observer class StockInput extends Component{
@observable input="";
render(){
return(
<div>
<input value={this.input} onChange={this.onChange}/>
<button onClick={this.onSubmit}>查询</button>
</div>
);
}
@action onChange=(e)=>{
this.input = e.target.value;
};
@action onSubmit = () =>{
this.props.stockStore.fetchStockByCode(this.input);
}
}
@inject("stockStore")
@observer class StockInfoView extends Component{
//常见陷阱——常见的错误的是从 observable 属性中提取数据并存储,这样的数据是不会被追踪的
//不要拷贝observables 属性并存储在本地
//Observer 组件只会追踪在 render 方法中存取的数据。
@computed get stockModel(){
return this.props.stockStore.stocks[0];
}
render(){
const {code,stockName,tPrice,nPrice,yPrice,hPrice,lPrice} = this.stockModel;
return(
<ul>
<li>股票代码:{code}</li>
<li>股票名称:{stockName}</li>
<li>今日开盘价:{tPrice}</li>
<li>昨日收盘价:{yPrice}</li>
<li>当前价格:{nPrice}</li>
<li>今日最高价:{hPrice}</li>
<li>今日最低价:{lPrice}</li>
</ul>
);
}
}
export default StockPage;
第六步:连接 Store 和视图层并加入 mobx-react-devtools
React开发的视图层和 MobX开发的Store 现在都已开发完毕。
视图层只负责 UI 的展示,Store 也会集中管理 state 。
现在我们需要将其连接起来使得视图层中能获取到 Store 保存的 state 值,以及视图层能触发 Store 中定义的操作 state 的 action 。
通过使用mobx-react
中提供的 Provider 组件来在React中使用MobX。
Provider 是一个 React 组件,利用 React 的 context 机制把应用所需要的 state 传递给子组件。
它的作用与 react-redux 提供的 Provider 组件是相同的。
import { Provider } from "mobx-react";
import React from "react";
import ReactDOM from 'react-dom';
import DevTools from 'mobx-react-devtools';
import StockPage from "./components";
import stores from "./stores";
const App = ()=>(
<div>
<StockPage />
<DevTools/>
</div>
);
ReactDOM.render(
<Provider {...stores}>
<App />
</Provider>,
document.getElementById("root"));
第七步:运行我们的第一个示例应用
进入my-app
的目录:
在终端命令行输入:cd my-app
运行我们的应用:
在终端命令行输入:npm start
等待服务启动完毕后,在浏览器地址栏输入localhost:3000/
就可以看到我们的应用啦!
现在我们试试输入柳钢股份的股票代码601003
,点击查询按钮就可以看到柳钢股份的相关股票信息啦!( 没错!这可能是篇软广…… )
写在最后
相关前端代码和后端代码近期将会上传至 github 以供大家参考运行,还请大家耐心等候。
这仅仅是一个简单的 MobX+React 简单示例应用,如果想了解更多 MobX 的高级用法,请参阅 MobX API 。
如有任何疑问,敬请留言或者私信。