about 3 years ago

使用 create-react-app 來迅速建立開發環境

npm install -g create-react-app

建立開發專案資料夾

create-react-app react-mobx-todo-editor

由於預設的環境不支援 decorators 裝飾模式
需手動配置 Babel plugin
先執行 create-react-app eject 指令來解除 CLI 環境

npm run eject

對於 create-react-app 的內部結構及相關指令可參考底下連結
http://www.eloquentwebapp.com/create-react-apps/

安裝 Babel plugin decorators 裝飾模式

npm install babel-plugin-transform-decorators-legacy --save-dev

修改 create-react-app eject 後的 config 檔案
config/babel.dev.js 及 config/babel.prod.js
於 plugins 屬性內追加底下設定

plugins: [
    // handles @decorator
    require.resolve('babel-plugin-transform-decorators-legacy'),

測試 create-react-app 環境

npm run start

會自動導入一個瀏覽器頁面並開啟 development server 網址

安裝 mobx-react

npm install mobx mobx-react --save

建立 mobx model
Todo.js

import {observable, computed} from 'mobx'

// 建立 Todo 流水號
var _nextId = 0
function nextId(){ _nextId++; return _nextId }

// todo model
export class Todo{
    
    id = nextId();
    // 使用 @observable 來觀察變數的變化
    @observable text = '';
    @observable done = false;

    // 當 observable 的變數有變動時會執行底下函數
    @computed get isValid(){
        return this.text !== '';
    }
    
    // 建立 {} 物件
    serialize(){
        return {
            id: this.id,
            text: this.text,
            done: this.done
        }
    }
    
    // 解構 json 至 todo 物件
    static deserialize(json: Object){
        const todo = new Todo()
        todo.id = json['id'] || nextId()
        todo.text = json['text'] || ''
        todo.done = json['done'] || false
        return todo
    }    
}

建立 ViewModel
ViewModel.js

import {observable, action} from 'mobx'
import {Todo} from './Todo'

export class TodoViewModel{
    // 使用 @observable 來觀察變數的變化
    @observable todos = []

    // 使用建構式初始化 todos
    constructor(){
        this.load()
    }

    // 使用 @action 來綁定 event
    // 建立一組新的todo
    @action
    add(){
        const newTodo = new Todo()
        this.todos.push(newTodo)
        return newTodo
    }

    // 移除 todo
    @action
    remove(todo: Todo){
        // 移除 todo
        const index = this.todos.indexOf(todo)
        if(index > -1){
            this.todos.splice(index, 1)
        }
    }

    // 載入 window.localStorage 所記錄的 todos json
    @action
    load(){
        if(window.localStorage){
            const json = JSON.parse(window.localStorage.getItem("todos") || "[]")

            // 使用 Todo.deserialize 來解析 window.localStorage 所記錄的 todos json
            this.todos = json.map(todo => Todo.deserialize(todo))
        }
    }

    // 儲存所有 todos
    @action
    save(){
        // 使用 todo.isValid 來驗證 todo 格式是否正確
        if(this.todos.filter(todo => todo.isValid === false).length > 0){
            alert("Unable to save: There are invalid Todos.")
        }
        
        // 儲存所有 todos 至 window.localStorage
        // 使用 todo.serialize() 轉換到 {} 物件再利用 JSON.stringify 轉成 JSON 格式
        if(window.localStorage){
            window.localStorage.setItem(
                "todos", 
                JSON.stringify(
                    this.todos.map(todo => todo.serialize())
                )
            )
        }
    }
}

建立 TodoView.js

import React from 'react'
import {observer} from 'mobx-react'

// 使用 @observer 來觀察 model(TodoViewModel) 的動態
@observer
export class TodoView extends React.Component{
    
    render(){
        const model = this.props.model

        // 使用 props.model(TodoViewModel) @action 所定義的 event 來操作 model(TodoViewModel) 內的資料
        // 使用 props.model.todos 來傳遞 todo 給子view 呈現資料
        return <div>
            <h1>React & MobX Todo List!</h1>
            <p>
                <button onClick={() => model.add()}>New Todo</button>
                <button onClick={() => model.load()}>Reload Todos</button>
                <button onClick={() => model.save()}>Save Todos</button>
            </p>
            {model.todos.map((todo, i) => <SingleTodoView key={todo.id} model={model} todo={todo} />)}
        </div>
    }
}

// 使用 @observer 來觀察 model(Todo) 的動態
@observer
export class SingleTodoView extends React.Component{

    render(){
        const model = this.props.model
        const todo = this.props.todo
        
        // 使用 props.model(TodoViewModel) @action 所定義的 event 來操作 model(TodoViewModel) 內的資料
        // 使用 props.todo(Todo) 來配置 todo view 的內容
        return <p>
                    #{todo.id} 
                    <strong>{todo.text}</strong> 
                    <i>{todo.done ? 'DONE!' : ''}</i>
                    
                    <br/>

                    <input type="checkbox" checked={todo.done} onChange={e => {todo.done = e.target.checked}} />
                    <input type="text" value={todo.text} onChange={e => {todo.text = e.target.value}} />
                    <button onClick={() => model.remove(todo)}>Delete</button>
                </p>
    }
}

建立 index.js

import React from 'react';
import {render} from 'react-dom';
import {TodoView} from './TodoView';
import {TodoViewModel} from './TodoViewModel';

// 建立 TodoViewModel
const model = new TodoViewModel();

// 將 TodoViewModel 傳入 TodoView
render(<TodoView model={model} />, document.getElementById('root'));

檢視結果

npm run start

開發流程
1.Todo 包含一組todo 的基本資料欄位及檢查運算式 isValid 和資料格式轉換 serialize 及 deserialize
2.TodoViewModel 的 todos 包含了所有的 Todo 資料, 並建立處理 todos 資料的 event
3.TodoView 的 TodoView Component 處理 TodoViewModel 內的 todos 資料及event
4.TodoView 的 SingleTodoView Component 接收及觀察 Todo 內的資料, 並使用 TodoViewModel.remove(todo) 刪除自身資料
5.index.js 建立 TodoViewModel 並傳遞給 TodoView 做 render

更詳細的 mobx MVVM 作法請參考官方網址
https://github.com/mobxjs/mobx

參考網址
https://medium.com/MattiaManzati/building-a-react-mobx-application-with-mvvm-ec0b3e3c8786#.rwee0jx8y@

← 簡單介紹 react-motion 的 Motion Component 使用 VS Code 的 Extension Debugger for Chrome →
 
comments powered by Disqus