您的位置:首页 > 编程语言

RN TODO代码解析之三 (WithMobX!)

2017-12-11 10:21 162 查看
终于来到了这一篇,前面的内容实际是为了让我们理解这个demo,这样我们使用MobX来管理数据和状态的时候,我们才能比较好的理解我们使用MobX的意义。
在前面两篇的代码里,我们可以看到在TodoApp类里面,不光包含了UI相关的代码,还包含了对model的操作实现(itemmodel和listmodel),且列表项的操作也在TodoApp类里面实现了。
让我们拿iOS开发做一个类比:这就好像是把所有的业务逻辑(包括cell的内部逻辑)都放在了一个vc里面,如果功能变得复杂,那么我们的代码就会越来越难以维护,因为没有一个清晰的结构。Mobx作为一个和redux同类型的库,它让我们很容易就剥离出model和相关的业务逻辑(在实现这一点的同时我们也远离了state的传递和内部的管理,这让我们舒服了不少,毕竟我个人来看state的设计理念虽然比较不错,但从实际应用角度看却很麻烦,给代码编写和维护都带来很多问题,对比来看vue就更加简单务实).
来自官网的介绍:它通过透明的函数响应式编程(transparently applying functional reactive programming - TFRP)使得状态管理变得简单和可扩展。
所以从务实的角度来说,我们使用mobx的意义就是使代码变得更清晰,更简单,更易于扩展和维护。
下面就来看看我们的实现: 重构后的demo分为3个类,比之前多出了两个:TodoItem、TodoList; 从oop的角度这是很自然的。我们需要一个列表项的model;我们还需要一个列表作为列表的数据源,同时它还可以承载对列表model操作的封装。
TodoItem:

import {observable, action, computed} from 'mobx';
import {useStrict} from 'mobx';
useStrict(true);

// todoItem model.
export default class TodoItem
{
// 提供一个构造器方法用于方便创建对象;默认都从构造器方法创建。
constructor(text,done,index){
this.done = done?done:false;
this.text = text?text:'';
this.id = index;
this.key = index;
}

@observable done;
@observable text;
id;
key;

// 使用action来操作,语义更清晰!
@action toggle() {
console.log('toggle in item, done='+this.done)
this.done = !this.done;
console.log('2-toggle in item, done='+this.done)
}
}提供了一个构造器,以及一个toggle操作,改变done的状态。

TodoList:
import {observable, action, computed} from 'mobx';
import {useStrict} from 'mobx';
useStrict(true);
//import { reaction } from 'mobx/lib/api/autorun';

import TodoItem from './TodoItem';

// TodoList class; data model + actions!
class TodoList
{
// 初始化给有一条记录; 这里未提供构造器方法。
@observable list = [new TodoItem('Todo Item #1',false, 1)];
@observable isHideDone = false;

@action addTodo() {
const newTodo = this.makeTodo();
this.list = [newTodo, ...this.list];
}

@action makeTodo(index, isDone)
{
const id = index? index:this.list.length+1;

console.log('isDone='+isDone)
var newItem = new TodoItem('Todo Item #' + id,isDone, id);

return newItem;
}

@action toggleHidden() {
this.isHideDone = !this.isHideDone;
}

@computed get shownList() {
if (this.isHideDone)
{
return this.list.filter((item) => {
return !item.done;
});
}
else
{
console.log(this.list)
return this.list;
}
}
}

const SharedList = new TodoList();
export default SharedList;
// 多处共享时需要使用此方式导出保证只有一份实例;在我们这个场景下,可用可不用,都ok。提供了多个action; 这不是一个纯粹的model,但很实用。还提供了一个computed的值,用于两种状态的datasource切换。

App.js:
import React, {
Component,
PropTypes
} from 'react'

import {
AppRegistry,
StyleSheet,
View,
Text,
Image,
Button,
TextInput,
ListView,
Switch,
TouchableOpacity,
FlatList
} from 'react-native'

import { observable } from "mobx";
import { observer } from 'mobx-react/native';
import SharedList from './stores/TodoList';

@observer
class TodoApp extends Component {

constructor(props) {
super(props);
// 多加一条记录
props.list.addTodo();
}

render() {
// 这里用observer包裹,非常关键! 参看这里:
//https://github.com/SangKa/mobx-docs-cn/blob/master/docs/best/react.md#mobx-只会为-observer-组件追踪数据存取如果数据是直接通过-render-进行存取的
const ItemRenderer = observer(({ item, index }) => (
<View style={styles.todo} key={item.id}>
<View>
<Switch onValueChange={() => item.toggle()} value={item.done} />
</View>
<View>
<Text>{item.text}</Text>
</View>
</View>
)
)

return (
<View style={styles.container} >
<View style={styles.options}>
<TouchableOpacity onPress={() => this.props.list.addTodo()} style={styles.add}>
<Text>+ Add a todo</Text>
</TouchableOpacity>
<View style={styles.hide}>
<Text>Hide done</Text>
<Switch
onValueChange={(value) => this.props.list.toggleHidden()}
value={this.props.list.isHideDone} />
</View>
</View>

<FlatList
data={this.props.list.shownList}
renderItem={({ item, index }) => <ItemRenderer item={item} index={index} />}
/>
</View>
);
}
}

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
},
add: {
flex: 1,
padding: 10
},
hide: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around'
},
options: {
flexDirection: 'row',
marginTop: 50,
marginBottom: 30
},
todo: {
flex: 1,
flexDirection: 'row',
marginBottom: 10
}
});

export default class App extends React.Component {
render() {
return <TodoApp list={SharedList} />;
}
}
可以看到这里我们基本上使用JSX和styleSheet完成了所有的工作,这个类更接近于一个纯粹的View了,在this.props.list里面承载了mobx观察的对象。我们只是在合适的时候调用了对应model的方法,这样的结构会让你觉得舒服吗?
遇到唯一麻烦的问题在于:
const ItemRenderer = observer(({ item, index }) => (
<View style={styles.todo} key={item.id}>
<View>
<Switch onValueChange={() => item.toggle()} value={item.done} />
</View>
<View>
<Text>{item.text}</Text>
</View>
</View>
)
)这里原本我是直接用箭头函数和renderItem方法绑定的,但却发现这个switch的显示一直不正确。经过调试和文档查阅、代码review我找到了答案:
这里如果直接绑定renderItem的回调方法,那么我们在TodoApp类里面的@observer实际上是不能够对这方法内部的观察值进行观察的。因为该方法的执行实际上是在flatlist里面完成,我们没有办法去改造flatlist的代码。那么解决的办法也很简单,mobx给我们提供了两个选择:
//https://github.com/SangKa/mobx-docs-cn/blob/master/docs/best/react.md#mobx我们使用了看起来更直观的一种,使用observer包裹了创建列表项的箭头函数,这样我们就可以对这个方法内部的观察值进行观察,当其值发生变化的时候我们就可以正常触发render了。

PS:阅读本文遇到不理解的地方请随时查阅mobx官方文档的基本概念。 http://cn.mobx.js.org/best/react.html
这只是一个简单的开始,mobx的强力取决于你对它的使用,其概念和实现的威力可能会远超你的想象。

附(github链接, 如果帮到你欢迎给个star): https://github.com/dustturtle/RNMobXTodoDemo
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: