Mobx运用在react
准备:
- 此文章里的demo以create-react-app,vue-cli的默认demo为基础。
- mobx需要es7的装饰器语法,所以需要对项目进行一定的修改与配置,需要先eject。如果不eject也可以,但是不推荐,方法也在后面引用的链接里。详见create-react-app + mobx其中@observer装饰器报错
demo功能很简单,效果及说明如下:
说明:
- full name = first name + last name,且响应式显示
- 点reset按钮,清空所有数据
- fist name,last name都为空时,full name显示提示语
分析:
变量:firstName,lastName
方法:getFullName、doReset(为了方便,react还要封装一个setInputValue方法,让input监听onchange时调用)
react、vue、react + mobx三种方案的代码实现对比:
react实现:
-
import React, { Component } from 'react';
-
import ReactDOM from 'react-dom';
-
import registerServiceWorker from './registerServiceWorker';
-
class App extends Component {
-
constructor(props) {
-
super(props);
-
this.state = {
-
firstName: '',
-
lastName: ''
-
}
-
}
-
setValue(key, event) {
-
this.setState({
-
[key]: event.target.value
-
});
-
}
-
getFullName() {
-
let { firstName, lastName } = this.state;
-
if (!firstName && !lastName) {
-
return 'Please input your name!'
-
} else {
-
return firstName + ' ' + lastName;
-
}
-
}
-
doReset() {
-
this.setState({
-
firstName: '',
-
lastName: ''
-
})
-
}
-
render() {
-
const st = this.state;
-
const fullName = this.getFullName();
-
return (
-
<div>
-
<h1>This is normal react!</h1>
-
<p>First name: <input type="text" value={st.firstName} onChange={e => this.setValue('firstName', e)} /></p>
-
<p>Last name: <input type="text" value={st.lastName} onChange={e => this.setValue('lastName', e)} /></p>
-
<p>Full name: {fullName}</p>
-
<p><button onClick={() => { this.doReset() }}>Reset</button></p>
-
</div>
-
);
-
}
-
}
-
ReactDOM.render(<App />, document.getElementById('root'));
-
registerServiceWorker();
vue实现:
-
<template>
-
<div>
-
<h1>This is vue!</h1>
-
<p>First name: <input type="text" v-model="firstName" /></p>
-
<p>Last name: <input type="text" v-model="lastName" /></p>
-
<p>Full name: {{fullName}}</p>
-
<p><button @click="doReset">Reset</button></p>
-
</div>
-
</template>
-
<script>
-
export default {
-
data() {
-
return {
-
firstName: "",
-
lastName: ""
-
};
-
},
-
computed: {
-
fullName() {
-
const { firstName, lastName } = this;
-
if (!firstName && !lastName) {
-
return "Please input your name!";
-
} else {
-
return firstName + " " + lastName;
-
}
-
}
-
},
-
methods: {
-
doReset() {
-
this.firstName = "";
-
this.lastName = "";
-
}
-
}
-
};
-
</script>
对比:
- 模板,vue因为有自己的语法糖,所以较简洁,这点优势不算大。但有一点需要注意一下,react要生成fullName需要在render里调用getFullName方法,这个其实有点让render不太“纯”(只负责渲染,业务逻辑尽量少)了。而vue引入了computed的概念,值得借鉴。
- 方法,按照性质来说,方法可以分为两类,就像vue把方法分成了computed和methods。而react的所有方法都写在一起,当方法特别多的时候,比较难区分,虽然可以通过一些约定来形成一些规范,但是实在难保证效果,vue的思想值得借鉴。
- 赋值,react需要setState,vue则直接赋值(起码在语法上是),虽然觉得react这种写法可以使数据在变化时,特别容易识别,但是写起来还是有点麻烦。。。
区别还能说出来更多,但是最主要的就是这几点,那么当react遇到mobx时会发生什么呢?
mobx + react实现:
-
import React, { Component } from 'react';
-
import ReactDOM from 'react-dom';
-
import registerServiceWorker from './registerServiceWorker';
-
import { observable, computed, useStrict, action } from 'mobx';
-
import { observer } from 'mobx-react';
-
useStrict(true)
-
class VM {
-
@observable firstName = '';
-
@observable lastName = '';
-
@computed get fullName() {
-
const { firstName, lastName } = this;
-
if (!firstName && !lastName) {
-
return 'Please input your name!'
-
} else {
-
return firstName + ' ' + lastName;
-
}
-
};
-
@action.bound
-
setValue(key, event) {
-
this[key] = event.target.value;
-
}
-
@action.bound
-
doReset() {
-
this.firstName = '';
-
this.lastName = '';
-
}
-
}
-
@observer
-
class App extends Component {
-
render() {
-
const vm = this.props.vm;
-
return (
-
<div>
-
<h1>This is mobx-react!</h1>
-
<p>First name: <input type="text" value={vm.firstName} onChange={e => vm.setValue('firstName', e)} /></p>
-
<p>Last name: <input type="text" value={vm.lastName} onChange={e => vm.setValue('lastName', e)} /></p>
-
<p>Full name: {vm.fullName}</p>
-
<p><button onClick={vm.doReset}>Reset</button></p>
-
</div>
-
);
-
}
-
}
-
ReactDOM.render(<App vm={new VM()} />, document.getElementById('root'));
-
registerServiceWorker();
那几个@xxxx虽然可能不太知道是啥意思,但是也差不多能猜出来八九不离十。仔细对比代码后可以发现:
- 模板,照react变化不大,只是少了fullName之类的computed方法,让render变得更干净。而且render只与props有关,所以结合无状态组件来使用,就只剩下模板了,非常干净,这个大家可以想象一下。
- 方法,可以看到VM部分,其实就相当于vue的VM(scrpit部分),而且分为了数据,计算属性,方法三个部分。还是比较清晰的。
- 最后,没有了setState!没有了setState!没有了setState!自动监听了啊有木有!声明时一个@observable就解决了啊有木有!活脱脱变成vue的双胞胎了啊有木有!
总结:
vue作者尤雨溪如是说:
结合react可以直接引用外部模块这点(通常是保存公共变量或方法的模块,这点vue需要靠vuex来实现,所以在这点上,react是更方便的),mobx可以替代redux作为项目的数据处理方案,可参考:【译】Redux 还是 Mobx,让我来解决你的困惑!
最后,如果有必要,再封装一些语法糖,甚至可以做到比vue还要简洁。当然,会额外增加学习成本,这个就需要自己权衡了。
最后附上用无状态组件实现的代码,大家可以品一下
父组件index
-
// index.js
-
import React from 'react';
-
import ReactDOM from 'react-dom';
-
import registerServiceWorker from './registerServiceWorker';
-
import { App, AppVM } from './App';
-
ReactDOM.render(<App vm={new AppVM()} />, document.getElementById('root'));
-
registerServiceWorker();
子组件App,无状态组件
-
// App.js
-
import React from 'react';
-
import { observer } from 'mobx-react';
-
import { observable, computed, action } from 'mobx';
-
export const App = observer(({ vm }) => (
-
<div>
-
<h1>This is mobx-react!</h1>
-
<p>First name: <input type="text" value={vm.firstName} onChange={e => vm.setValue('firstName', e)} /></p>
-
<p>Last name: <input type="text" value={vm.lastName} onChange={e => vm.setValue('lastName', e)} /></p>
-
<p>Full name: {vm.fullName}</p>
-
<p><button onClick={vm.doReset}>Reset</button></p>
-
</div>
-
))
-
export class AppVM {
-
@observable firstName = '';
-
@observable lastName = '';
-
@computed get fullName() {
-
const { firstName, lastName } = this;
-
if (!firstName && !lastName) {
-
return 'Please input your name!'
-
} else {
-
return firstName + ' ' + lastName;
-
}
-
};
-
@action.bound
-
setValue(key, event) {
-
this[key] = event.target.value;
-
}
-
@action.bound
-
doReset() {
-
this.firstName = '';
-
this.lastName = '';
-
}
-
}
我故意把render放在了上面,怎么样,这么看是不是更像vue了点~