begin:2018-08-13css
step 1 熟悉react 写法html
step 2 mobx 了解&使用vue
step 3 thrift接口调用过程node
propsType官方文档react
react能够在引入prop-types
,配置propsTypes属性以后进行类型检查。webpack
能够将属性声明为JS原生类型、React元素、某个类的实例,指定类型的对象等,也能够自定义,能够加上isRequired后缀,若是没有提供该信息,会打印警告。git
还能够经过配置defaultProps
,为props定义默认值。es6
react childrengithub
class Grid extends React.Component { constructor(props) { super(props) this.state = {} } render() { return ( // 能够在这里控制子元素是否显示 <div>{this.props.children}</div> // 只显示hello文本,并不显示子元素 // <div>hello</div> ) } } const Row = ({ name }) => { return ( <div>{name}</div> ) } ReactDom.render( <Grid> <Row name={1} /> <Row name={2} /> <Row name={3} /> </Grid>, document.getElementById('root') )
mobx是一个状态管理器,下图是官网的原理图,看上去感受跟Vue的双向数据绑定很类似。web
经过action来修改组件状态,因为数据与视图双向绑定,一旦数据改变,会触发视图的更新,从而引发组件或者页面的从新渲染。
mobx的computed与vue的计算属性也有相似,都设置了缓存,依赖项没有发生变化的时候,该属性不会从新运行计算,只有在真正须要更新的时候才会更新。设置了computed的方法与普通方法的区别,也相似于vue的computed与method的区别。
感受简单而言,从视图更新的过程来看,能够抽象成三个部分:action、state、views,mobx单项数据流,能够有下图的描述:
我以为,State若是类比于MVVM的话,能够理解为ViewModel。
从开发者的角度来看:
本地须要搭建一个react-app环境并添加mobx等相关依赖。
step:
create-react-app my-react-app
使用命令行工具建立新的react-app,并进入项目目录(本地需先使用npm install -g create-react-app
命令安装工具)
npm install --save-dev babel-core babel-cli babel-preset-env babel-preset-react
建立&编写.babelrc
文件
(这里的plugins
若是不写也能够,关于支持ES7装饰器的配置问题,后面会再讲)
{ "presets": [ "env", "react", "stage-1", "es2015" ], "plugins": [ "transform-decorators-legacy", "transform-decorators" ] }
安装其余依赖,包括style-loader
、babel-loader
、css-loader
等等。
这里我开始手动安装了webpack
,而后安装webpack
的时候,没有指定版本号,默认会安装最新版本Webpack4,运行时会报下面错误:
Cannot read property 'thisCompilation' of undefined during npm run build
参考这里的解决方式
To solve this problem:
- Delete
node_modules
- Delete
package-lock.json
if present- If you have
react-scripts
inpackage.json
, make sure you don't havewebpack
in it- Run
yarn
(ornpm install
)- Also make sure you don't have
package.json
ornode_modules
in the parent folders of your project
另外一种方式是webpack降级到3。能够理解成webpack4与react-scripts不能同时在package.json中存在。
查找资料的时候发现,若是使用Create React App
的话,实际上是不须要手动再去安装Webpack的。
最后我删除了node_modules
,而后package.json
中删除了webpack
,从新npm install
或者yarn
一下,问题解决了。
安装babel-plugin-transform-decorators
、 babel-plugin-transform-decorators-legacy
等相关依赖。
实际状况是,依赖装完,.babelrc
文件中也配置了插件,webpack也配置完成以后,仍然没法识别装饰器语法,最后按照参考中的方法2解决了。可是这种方法须要在node_modules
中修改,我的以为不大好,暂时先这样处理下,后续再查看下。
npm run start
启动项目,进行后续操做。参考学习了 egghead.io课程
入门demo:
import { observable } from 'mobx'; import { observer } from 'mobx-react'; import { Component } from 'react'; import React from "react"; import ReactDOM from "react-dom"; const appState = observable({ count: 0 }) // 这里不能写成剪头函数 不然数据绑定会失效 appState.increment = function () { this.count++; } appState.decrement = function () { this.count--; } @observer class Counter extends Component { render() { return ( <div> Counter {this.props.store.count} <br /> <button onClick={this.handleInc}> + </button> <button onClick={this.handleDec}> - </button> </div> ) } handleInc = () => { this.props.store.increment() } handleDec = () => { this.props.store.decrement() } } const rootElement = document.getElementById("root"); ReactDOM.render(<Counter store={appState} />, rootElement);
mobx使用ES7装饰器语法(可选使用)经过给现有属性增长@observable
注解,就能够将属性设定为可观察的属性。使用装饰器属性可使得书写代码更加简洁.
也能够写成这样
class appState { @observable count = 0; increment = function () { this.count++; } decrement = function () { this.count--; } } ...... ReactDOM.render(<Counter store={new appState()} />, rootElement);
不过有个疑惑,下图方法1使用const定义是出于什么目的,官方文档的demo中也有不少是使用const定义的。
若是像下图方法二这样书写,是否有意义?count值也会改变,appState定义为const,其中内容是能够被改变的,如何控制不被改变?实际中是否会有这种状况?
//1. 这里写成const是什么意义? const appState = observable({ count: 0 }) //2. 这样写是否有意义?const? const appState = { @observable count: 0 }
使用@computed 修饰getter方法,计算值延迟更新,只有依赖改变,须要从新计算的时候才会更新。
只要须要在状态发生改变时须要更新视图的view上使用@observer修饰,就能够实现自动更新。
actions执行状态的改变。
文档中有这么一段,我的以为全部衍生同步更新,计算值延迟更新,这两句彷佛有些矛盾,这里的全部衍生是否指的是reactions或者action后出发的事件?意思是说不能用计算值来改变状态,而是状态改变以后计算值必定已经变化?有点拗口。。。这里的同步更新和延迟更新到底指的是什么,感受只能后面有时间看下源码才能了解了
当 状态改变时,全部 衍生( 任何 源自 状态而且不会再有任何进一步的相互做用的东西就是衍生 )都会进行 原子级的自动更新。所以永远不可能观察到中间值。全部衍生默认都是同步更新。这意味着例如动做能够在改变状态以后直接能够安全地检查计算值。
计算值 是延迟更新的。任何不在使用状态的计算值将不会更新,直到须要它进行反作用(I / O)操做时。 若是视图再也不使用,那么它会自动被垃圾回收。
全部的计算值都应该是纯净的。它们不该该用来改变状态。
observable
传递对象时,后添加到对象的属性没法自动变成可观察的状态这点有点相似于Vue中的对象数据绑定,若是在最开始定义的时候没有定义某个属性,后面再添加时将没法监控到这个属性的变化,可使用vue.set
来使得操做生效。
当使对象转变成 observable 时,须要记住一些事情:
- 当经过
observable
传递对象时,只有在把对象转变 observable 时存在的属性才会是可观察的。 稍后添加到对象的属性不会变为可观察的,除非使用set
或extendObservable
。
Array.isArray(observable([]))
返回值为false
observable.array
会建立一我的造数组(类数组对象)来代替真正的数组。 实际上,这些数组能像原生数组同样很好的工做,而且支持全部的原生方法,包括从索引的分配到包含数组长度。请记住不管如何
Array.isArray(observable([]))
都将返回false
,因此不管什么时候当你须要传递 observable 数组到外部库时,经过使用array.slice()
在 observable 数组传递给外部库或者内置方法前建立一份浅拷贝(不管如何这都是最佳实践)总会是一个好主意。 换句话说,Array.isArray(observable([]).slice())
会返回true
。
不一样于sort
和reverse
函数的内置实现,observableArray.sort 和 observableArray.reverse 不会改变数组自己,而只是返回一个排序过/反转过的拷贝。
computed&autorun并不同。
两者都是响应式调用的衍生,可是computed能够理解为一个纯函数(即调用时刻的输出只由该时刻的输入决定,而不依赖于系统状态),若是使用过程当中依赖没有被修改,则不会从新计算。autorun的使用场景更像是产生效果,例如对数据进行过滤操做(而不是产生数据),或者数据监控到数据变化以后的通知等反作用操做(这点与vue中的method并不同,不要混淆)。
若是你想响应式的产生一个能够被其它 observer 使用的 值,请使用@computed
,若是你不想产生一个新值,而想要达到一个 效果,请使用autorun
。 举例来讲,效果是像打印日志、发起网络请求等这样命令式的反作用。
错误处理
若是计算值在其计算期间抛出异常,则此异常将捕获并在读取其值时从新抛出。 强烈建议始终抛出“错误”,以便保留原始堆栈跟踪。抛出异常不会中断跟踪,全部计算值能够从异常中恢复。
const x = observable(3) const y = observable(1) const divided = computed(() => { if (y.get() === 0) throw new Error("Division by zero") return x.get() / y.get() }) divided.get() // 返回 3 y.set(0) // OK divided.get() // 报错: Division by zero divided.get() // 报错: Division by zero y.set(2) divided.get() // 已恢复; 返回 1.5
const myProp = props.myProp
)。否则,若是你在 reaction 中引用了 props.myProp
,那么 props 的任何改变都会致使 reaction 的从新运行。陷阱
const message = observable({ title: "hello" }) autorun(() => { // 错误 console.log(message) // 正确 console.log(message.title) }) // 不会触发从新运行 message.title = "Hello world"
其余解决方案:
autorun(() => { console.log(message.title) // 很显然, 使用了 `.title` observable }) autorun(() => { console.log(mobx.toJS(message)) // toJS 建立了深克隆,从而读取消息 }) autorun(() => { console.log({...message}) // 建立了浅克隆,在此过程当中也使用了 `.title` }) autorun(() => { console.log(JSON.stringify(message)) // 读取整个结构 })
runInAction
是个简单的工具函数,它接收代码块并在(异步的)动做中执行。今天使用react+mobx 写了个todolist的demo,目前实现了添加和删除的功能。熟悉一下开发方式和书写方式。
主要代码:
import React, { Component } from 'react' import { observable, computed, observe, action } from 'mobx'; import ReactDOM from 'react-dom'; import { inject, Provider, observer } from 'mobx-react' import './index.css'; import { debug } from 'util'; class Todo { constructor(content) { this.content = content } id = Math.random() @observable content @observable completed = false } class TodoListStore { @observable todos = [] @computed get todosLength() { return this.todos.length } @computed get completedLength() { return this.todos.filter(item => item.completed).length } @computed get uncompletedLength() { return this.todosLength - this.completedLength } @action addTodo(todo) { this.todos.push(todo) } @action deleteTodo(index) { this.todos.splice(index, 1) // console.log(e) } } // const TodoItem = observer(({ todo }) => ( // <li> // <input // type="checkbox" // checked={todo.completed} // onClick={() => (todo.completed = !todo.completed)} // /> // {todo.content} // <button onClick={}>X</button> // </li> // )); @observer class TodoItem extends Component { constructor(props) { super(props) } render() { const {todo, index} = this.props return ( <li> <input type="checkbox" checked={todo.completed} onClick={() => (todo.completed = !todo.completed)} /> {todo.content} <button onClick={this.props.del.bind(this)}>X</button> </li> ) } } @observer class TodoInput extends Component { constructor(props) { super(props) this.state = new Todo() } addTodo() { let content = this.refs.content.value let item = new Todo(content) this.props.store.addTodo(item) console.log(this.props.store.todos) this.refs.content.value = '' } render() { return ( <div> 新增todo: <input className="todo-input-box" type="text" ref="content" placeholder="add something"></input> <button onClick={this.addTodo.bind(this)}>Add</button> </div> ) } } @observer class TodoList extends Component { constructor(props) { super(props) this.state = { todos: this.props.store.todos, index: -1 } } delete(index) { this.props.store.deleteTodo(index) } render() { // let todos = [...this.props.store.todos] return ( <div> 事项清单: <ul> {this.state.todos.map((todo, index) => ( <TodoItem todo={todo} key={todo.id} index={index} del={()=>this.delete(index)}/> ) )} </ul> </div> ) } } @observer class TodoArchive extends Component { render() { let store = this.props.store return ( <div className="archieve"> <span className="archieve-item">总计:{store.todosLength}项</span> <span className="archieve-item">已完成:{store.completedLength}项</span> <span className="archieve-item">未完成:{store.uncompletedLength}项</span> </div> ) } } class TodoListView extends Component { render() { let store = this.props.store return ( <div className="list-view"> <TodoInput store={store} /> <TodoList store={store} /> <TodoArchive store={store} /> </div> ) } } let todolist = new TodoListStore() ReactDOM.render(<TodoListView store={todolist} />, document.getElementById('root'));