使用 mobx 结合 react 搭建的小 demo….
先看效果图:
预览地址:TodoList github 地址:mobx-react — todoList
1. 前言 本文主要介绍 mobx-react 与 mobx 的使用,最后完成 todolist。
此 demo 全部用 class 组件搭建完成,如需使用 hooks ,请参考mobx与react-hooks
2. 环境准备 需要在mobx—简明学习 的基础上。
安装 react一套 :
yarn add react react-dom prop-types
安装 babel-react相关插件
yarn add @babel/preset-react
安装 mobx-react
yarn add mobx-react
配置 webpack.config.js 文件
配置完成,开始编写文件。
3. mobx-react 说明: 看 mobx-react 官方文档
可知有如下 api:
observer(componentClass)
Observer —- Observer是一个React组件,它将观察者应用于组件中的匿名区域。
useLocalStore hook
useAsObservableSource hook
Server Side Rendering with useStaticRendering(服务端)
PropTypes
Provider and inject
disposeOnUnmount(componentInstance, propertyKey | function | function[])
可见,有两种 api,一种是针对 componentClass 的,一种是针对 hooks 的。
我们使用 componentClass,所以主要用到:
observer(componentClass) 使类重新render
PropTypes 判定proptype
Provider and inject 提供 store 和 注入对应props
具体的使用,请阅读 官方文档 , 官方文档写得蛮详细的。
4. 开始编写 文件结构:
4.1 store.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 import { observable, action, computed, observe } from "mobx" class Todo { id = Math .random() @observable title = "" @observable finished = false constructor ( title ){ this .title = title } @action.bound toggle(){ this .finished = !this .finished } } class Store { @observable todos = [] disposers = [] @action.bound createTodo( title ){ this .todos.unshift( new Todo( title ) ) } @computed get left(){ return this .todos.filter( todo => { return !todo.finished }).length } @action.bound removeTodo( todo ){ this .todos.remove( todo ) } constructor (){ observe( this .todos, change => { this .disposers.forEach( disposer => disposer() ) this .disposers = [] for ( let todo of change.object ){ var disposer = observe( todo, change => { console .log( change ) }) this .disposers.push( disposer ) } }) } } const store = new Store()const ss = { store } export default ss
4.2 index.jsx 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import React, { Component } from "react" import ReactDOM from "react-dom" import { observer, Provider } from "mobx-react" import { trace } from "mobx" import TodoHeader from "./conponent/TodoHeader.jsx" import TodoView from "./conponent/TodoView.jsx" import TodoFooter from "./conponent/TodoFooter.jsx" import ss from "./store.js" @observer class TodoList extends Component { render(){ trace() return ( <div className="todo-list" > <TodoHeader /> <ul> <TodoView /> </ul> <TodoFooter / > </div> ) } } ReactDOM.render( ( <Provider { ...ss } > <TodoList / > </Provider> ), document.querySelector("#root") )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import React, { Component } from "react" import { observer, inject } from "mobx-react" import { trace } from "mobx" @inject( allStore => { return { left: allStore.store.left } }) @observer class TodoFooter extends Component { render(){ trace() return ( <footer> { this .props.left } items unfinished... </footer> ) } } export default TodoFooter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 import React, { Component } from "react" import { observer, inject } from "mobx-react" @inject( "store" ) @observer class TodoHeader extends Component { state = { inputValue : "" } handleSubmit = ( e ) => { e.preventDefault() let store = this .props.store var inputValue = this .state.inputValue store.createTodo( inputValue ) this .setState({ inputValue : "" }) } handleChange = ( e ) => { e.persist() var inputValue = e.target.value this .setState({ inputValue: inputValue }) } render(){ return ( <header> <form onSubmit={ this .handleSubmit } > <input type="text" onChange={ e => this .handleChange( e )} value={ this .state.inputValue } className="input" placeholder=" what deeds to be finished? " /> </form> </ header> ) } } export default TodoHeader
4.5 TodoView.jsx 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import React, { Component, Fragment } from "react" import PropTypes from "prop-types" import { observer, inject } from "mobx-react" @inject( "store" ) @observer class TodoView extends Component { render(){ const store = this .props.store const { todos } = store return todos.map( todo => { return ( <li key={ todo.id } className="todo-item" > <TodoItem todo={ todo } /> <span className="delete" onClick={ e => store.removeTodo( todo ) } >X</span> </ li> ) }) } } @observer class TodoItem extends Component { static propTypes = { todo: PropTypes.shape({ id: PropTypes.number.isRequired, title: PropTypes.string.isRequired, finished: PropTypes.bool.isRequired }).isRequired } handleClick = () => { this .props.todo.toggle() } render(){ const todo = this .props.todo return ( <Fragment> <input type="checkbox" className="toggle" checked={ todo.finished } onChange={ this .handleClick } /> <span className={["title" , todo.finished && "finished" ].join(" " )} > { todo.title } </span> </ Fragment> ) } } export default TodoView
4.6 html 文件内添加 css 样式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 .input { padding : 15px ; border : 1px solid #ccc ; font-size : 24px ; width : 400px ; } .todo-item { display : flex; align-items : center; height : 40px ; border-bottom : 1px solid #ccc ; } .todo-item .toggle { margin-right : 20px ; } .todo-item .title { font-size : 24px ; color : #000 ; } .todo-item .title .finished { text-decoration : line-through; color : #ccc ; } .todo-item .delete { margin-left : 20px ; cursor : pointer; } footer { font-size : 22px ; }
5. 性能优化 提升性能三大法则:
细粒度拆分视图组件
使用专用组件处理列表
尽可能晚的结构可观察数据
上面三大法则,你在上面的代码中,能找到在哪里吗??
还有其他的优化点:
尽早的绑定函数(不要传匿名函数和箭头函数)
不要使用数组的索引作为 key
使用小组件
详情参考:优化 React 组件渲染
6. 总结 当得到一些点拨过后,再去阅读官方文档,就会收获更多,
当然也要学会阅读官方文档,官方文档写得都蛮好的。
写下这篇文章,也算是总结了一下 mobx 相关的知识.
加油,写下这篇文章希望对你有帮助,与君共勉!!
有时间,我会把 class 版本改成 hook 版本的。
7. 参考材料 茵风泳月–mobx入门基础教程
mobx—-官网
mobx-react 文档
mobx-react 官方文档