React實戰(zhàn):react+webpack+es6 實現(xiàn)簡易todo app

React可謂如日中天增蹭,webpack也風聲水起。React剛出來不久就瀏覽了一遍官網(wǎng)的文檔,當時想這個新玩意挺“顛覆”幔崖,暫時保持觀望好了。直到React Angular Vue三分天下的時候渣淤,還處于觀望就不太妥了赏寇。再次看完官網(wǎng)的document,嘗試實現(xiàn)一個todo應用來實踐react价认。

如果說實現(xiàn)一個Blog是后端工程師入門的第一個應用嗅定,那么Todo可謂是前端開發(fā)者練手處女項目了。
下面就使用React實現(xiàn)一個簡單的todo刻伊,實現(xiàn)基本的增刪改的功能露戒,其效果請訪問react-todo

創(chuàng)建項目

初始化項目

前端發(fā)展太快捶箱,從打包工具框架/庫都層出不窮智什,往往一個坑還沒爬出來,就掉進了另外的坑丁屎。創(chuàng)建的todo主要采用node包的方式荠锭,使用webpack打包,具體的js代碼使用ES6的語法晨川。

初始化項目并創(chuàng)建一些基礎文件证九,項目結構大概如下:

  ~  mkdir todos
  ~  cd todos && npm init
  todos  mkdir app app/components
  todos  touch index.html webpack.config.js
  todos  touch app/index.js app/components/app.js
  todos  tree
.
├── app
│   ├── components
│   │   └── app.js
│   └── index.js
├── index.html
├── package.json
└── webpack.config.js

2 directories, 5 files

安裝依賴包

初始化項目之后,就需要安裝所需要的庫及其依賴共虑。npm安裝方式可以為開發(fā)環(huán)境或生產(chǎn)選擇所安裝的依賴愧怜。首先需要安裝webpackwebpack-dev-server。這兩個包需要全局安裝妈拌,通過 npm install -g webpack webpack-dev-server拥坛,如果已經(jīng)安裝了,可以忽略尘分。

隨后將會安裝編譯ES6和JSX的編譯工具babel猜惋。運行下面命令安裝。react培愁,react-dom是react的基礎庫著摔,lodash則是一個函數(shù)庫,用于使用ES6的一些新特性定续。

  todos  npm install --save react react-dom lodash

上述包是生產(chǎn)發(fā)布環(huán)境也需要的依賴谍咆,下面安裝開發(fā)環(huán)境中使用的打包編譯的loader包:

  todos  npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react react-hot-loader style-loader css-loader webpack webpack-dev-server

如果一切順利禾锤,npm將會在package.json中顯示已經(jīng)安裝好的包。遷移項目的時候卧波,只需要npm install即可时肿。

配置webpack

安裝完所需要的依賴之后,配置webpack港粱。webpack的配置比較簡單螃成。具體配置如下:

var webpack = require("webpack")
var path = require("path")

module.exports = {
 devtool: "inline-source-map",
 entry: [
  "webpack-dev-server/client?http://127.0.0.1:8080/",
  "webpack/hot/only-dev-server",
  "./app"
 ],

 output: {
  path: path.join(__dirname, "public"),
  filename: "bundle.js"
 },
 resolve: {
  modulesDirectories: ["node_modules", "app"],
  extensions: ["", ".js"]
 },
 module: {
  loaders: [
   {
    test: /\.jsx?$/,
    exclude: /node_modules/,
    loaders: ["react-hot", "babel?presets[]=react,presets[]=es2015"]
   },
   {
    test: /\.css?$/,
    exclude: /node_modules/,
    loaders: ["style", "css"]
   }
  ]
 },
 plugins: [
  new webpack.HotModuleReplacementPlugin(),
  new webpack.NoErrorsPlugin()
 ]
}

關于webpack的配置,并不是本篇的主題查坪,想要了解更多的詳細內(nèi)容寸宏,可以查閱官網(wǎng)的文檔。

Hello world

配置了webpack之后偿曙,編寫html入口和js的入口文件氮凝,展示一下hello world啦。
編輯 index.html 文件

index.html

<!DOCTYPE html>
<html>
<head>
    <title>React Todos App</title>
</head>
<body>
    <div id="app" />
    <script src="bundle.js"></script>
</body>
</html>

入口的js文件

index.js

import React from 'react'
import {render} from 'react-dom'

render(<div>hello world</div> document.getElementById('app'))

運行 webpack-dev-server啟動webpack服務器望忆,使用瀏覽器打開 http://127.0.0.1:8080就能看見helloworld啦罩阵。這里webpack-dev-server是為了監(jiān)測前端文件的變化,以便實時編譯打包前端文件启摄。

React的render方法稿壁,將自定義的的component掛載到html中的dom中(div#app)。

React 組件

前面通過react的render方法歉备,創(chuàng)建了一個組件傅是,下面創(chuàng)建更多的組件。編輯 components/app.js

import React from "react"

class App extends React.Component {

    render() {
        return (
         <div>
          <h1>React Todo App</h1>
         </div>
        )
    }
}

export default App

然后修改之前的入口文件index.js蕾羊,將隨后創(chuàng)建的App組件渲染到html中喧笔。

index.js

import React from "react"
import {render} from "react-dom"
import App from "components/app"

render(<App />, document.getElementById("app"))

刷新瀏覽器,就能看見新創(chuàng)建的App組件龟再。

組件是React中的重要概念书闸。對于軟件界面,按鈕利凑,導航浆劲,表單這些可視化的界面都可以稱之為組件,組件實現(xiàn)了邏輯和功能的封裝截碴。就像完積木一樣,每個組件都是一個積木蛉威,多個積木可以合成一個大的積木日丹,最終實現(xiàn)組件構成的用戶界面。

React的組件都是用********大寫********的拉丁字母開頭蚯嫌,繼承自React.Components類哲虾。render方法用于返回該組件的JSX代碼丙躏。JSX是Facebook為了配合react定義的一套xml規(guī)范。與html及其相似束凑,用于構建組件界面晒旅。需要注意,JSX的所有標簽必須閉合汪诉。return之后必須返回一個組件元素废恋,不能同時返回多個,如果有多個的扒寄,需要用div重新包裝一次鱼鼓。

編寫Todo

todo 列表

配置好基本環(huán)境之后,接下來將要完成一個完整的todo應用该编。這個小應用主要有兩個大的組件迄本,一個是用于創(chuàng)建todo條目,另外一個用于展示todo列表课竣。下面先完成todo列表的組件嘉赎。隨著功能的增加,會經(jīng)常編輯某個文件于樟,文件內(nèi)容也會變多公条,在此只會貼出變更的代碼部分,不再貼出完整的文件內(nèi)容隔披,完整的文件內(nèi)容可以參考源碼赃份。

接下來在app.js 文件中定義數(shù)據(jù),數(shù)據(jù)的獲取方式很多奢米,假設現(xiàn)在從本地獲取數(shù)據(jù)抓韩。通過App這個組件逐漸把數(shù)據(jù)傳遞下去。定義了數(shù)據(jù)鬓长,需要借助React的state和props兩個屬性來實現(xiàn)數(shù)據(jù)傳遞谒拴。編輯app.js文件如下:

...
import TodoList from "components/todo-list"

const todos = [
 {
  task: 'Learning React',
  isCompleted: true
 },
 {
  task: 'Learning Jsx',
  isCompleted: false
 },
 {
  task: 'React in action',
  isCompleted: false
 }
]

class App extends React.Component {

 constructor(props){
  super(props)
  this.state = {
   todos: todos
  }
 }

    render() {
        return (
         <div>
          <h1>React Todo App</h1>
          <TodoList todos={this.state.todos}/>
         </div>
        )
    }
}

export default App;

propsstate 是react組件中重要的兩個屬性。它們本質(zhì)都是js對象涉波。props常用于存儲一些不可變的組件屬性英上,例如函數(shù)和方法,state則用于保留一些可變的數(shù)據(jù)結構啤覆,例如實際的數(shù)據(jù)和狀態(tài)tag苍日。

上述的代碼定義了一些todos數(shù)據(jù),然后把這些數(shù)據(jù)初始化給App組件窗声。再通過TodoList組件的todo props傳遞給后者相恃。也就是在TodoList內(nèi)部,它的this.props.todos則為 App組件的this.state.todos笨觅。

TodoList是用于展示todo列表的組件拦耐。再創(chuàng)建一個文件耕腾。

  app  touch components/todo-list.js

編輯todo-list.js 如下:

todo-list.js
...
import _ from 'lodash'

class TodoList extends React.Component {

 renderItem(){
  return  _.map(this.props.todos, (todo, index) => {
   return (
    <tr key={index}>
     <td>{todo.task}</td><td>{todo.isCompleted ? 'done' : 'undo'}</td>
    </tr>
   )
  })
 }

    render() {
        return (
         <table>
          <thead>
           <tr>
            <th>Task</th><th>Action</th>
           </tr>
          </thead>
          <tbody>
           {this.renderItem()}
          </tbody>
         </table>
        )
    }
}

export default TodoList;

TodoList組件由兩部分組成,table的head和body部分杀糯。body通過一個表格的行來展示todo的列表內(nèi)容扫俺。通過lodash的map方法,可以迭代一個數(shù)組(this.props.todos)對象固翰,然后把todo的列表拼裝成表格返回狼纬。最后在tbody中調(diào)用函數(shù)renderItem。至此倦挂,大致的一個todo應用輪廓已經(jīng)成形畸颅。接下來將要把TodoList這個組件更細化的拆分。主要拆分為head和item兩個組件方援。

TodoHeader 組件

創(chuàng)建一個組件文件没炒,用于表示todo應用的表頭。

  app  touch components/todo-header.js

修改編輯的todo-list.js 文件

import TodoListHeader from "components/todo-list-header"

class TodoList extends React.Component {

 ...

    render() {
        return (
         <table>
          <TodoListHeader />
          <tbody>
           {this.renderItem()}
          </tbody>
         </table>
        )
    }
}

然后再編輯 todo-list-header.js

import React from 'react'

class TodoListHeader extends React.Component {

    render() {
        return  (
          <thead>
                <tr>
                    <th>Task</th>
                    <th>Action</th>
                </tr>
            </thead>
        )
    }
}

export default TodoListHeader

TodoListHeader 組件相當簡單犯戏,只需要把thead的內(nèi)容copy即可送火。

TodoListItem 組件

需要拆分列表組件稍微復雜一點點。因為針對todo的每一個列表先匪,都有修改种吸、刪除的操作。因此這些事件可以封裝成為一個單獨的組件呀非,即item是組件坚俗。編輯todo-list.js文件,修改render函數(shù)如下:

todo-list.js

...

import TodoListItem from "components/todo-list-item"

class TodoList extends React.Component {

 renderItem(){
  return  _.map(this.props.todos, (todo, index) => {
   return (
    <TodoListItem todo={todo} key={index}/>
   )
  })
 }

 ...
}

然后編輯todo-list-item.js文件岸裙,增加TodoListItem組件猖败。和TodoListHeader組件類似,將之前renderItem中的jsx拷貝一份降允,通過this.props讀取單條todo的數(shù)據(jù)即可恩闻。

由于 react使用了virtrul-dom來實現(xiàn)操作dom的性能。那么針對一些列表元素的dom剧董,都需要給他們一個id幢尚,這個id可以使用 key={index} 來指定。

todo-list-item.js

import React from 'react'

class TodoListItem extends React.Component {
    render() {
        return (
            <tr key={this.props.index}>
                <td>{this.props.todo.task}</td><td>{this.props.todo.isCompleted ? 'done' : 'undo'}</td>
            </tr>
        )
    }
}

export default TodoListItem

Todo 創(chuàng)建

完成了todo列表的基本功能翅楼,下一步需要實現(xiàn)todo的創(chuàng)建功能尉剩。需要引入一個新的組建,TodoCreate毅臊。創(chuàng)建一個文件todo-create.js理茎。編寫如下內(nèi)容:

todo-create.js

import React from 'react';

class TodoCreate extends React.Component {
    render() {
        return (
         <form>
          <input type="text" placeholder="What need I do?" ref="createInput" />
          <button>Create</button>
         </form>
        )
    }
}

export default TodoCreate

然后編輯app.js 文件,引入TodoCreate 組件。

app.js

import TodoCreate from "components/todo-create"

class App extends React.Component {
   ...
    render() {
        return (
            <div>
                <h1>React Todo App</h1>
                <TodoCreate />
                <TodoList todos={this.state.todos}/>
            </div>
        )
    }
}

React事件

TodoCreate組件實質(zhì)是一個表單功蜓,一個表單域和提交按鈕。button的點擊事件會觸發(fā)form的onsubmit事件宠蚂。因此需要定義form的事件式撼,同時給表單域提供了一個ref屬性,用于react引用表單域?qū)ο蟆?/p>

todo-craete.js

class TodoCreate extends React.Component {
    render() {
        return (
            <form onSubmit={this.handleCreate.bind(this)}>
                <input type="text" placeholder="What need I do?" ref="createInput" />
                <button>Create</button>
            </form>
        )
    }

    handleCreate(event){
        event.preventDefault()

        const task = this.refs.createInput.value
        this.refs.createInput.value = ''
    }
}

給from增加了onSubmit事件函數(shù)handleCreate求厕。handleCreate函數(shù)中先把form的默認事件除去著隆,然后通過ref屬性獲取了表單的值。如果按照之前的編程習慣呀癣,此時這里可以處理增加todo的實際操作美浦。可是如果這里增加了todo项栏,那么如何渲染到todo列表的組件中呢浦辨?

實際上,TodoCreate和TodoList是同級的組件沼沈,他們通信的共同點是通過App組件流酬,并且之前的數(shù)據(jù)源都是通過App組件往子組件傳遞。因此可以在App組件中定義函數(shù)用于操作todo的數(shù)據(jù)列另,子組件只需要在自己的事件函數(shù)中調(diào)用父組建函數(shù)實現(xiàn)數(shù)據(jù)通信芽腾。

React 的事件和原生的js事件很像,只是寫法上使用駝峰式页衙,并且還保證了瀏覽器的兼容性摊滔。這樣的處理react隨處可見,例如后面將會遇到的樣式寫法店乐。ref是表單中常用的屬性艰躺,用于引用一個dom元素。

編輯App.js文件

app.js

...

class App extends React.Component {
  ...

    render() {
        return (
            <div>
                <h1>React Todo App</h1>
                // 綁定createTask函數(shù)給子組件TodoCreate
                <TodoCreate createTask={this.createTask.bind(this)}/>
                <TodoList todos={this.state.todos}/>
            </div>
        )
    }
   // 增加createTask函數(shù)用于接受處理TodoCreate組件創(chuàng)建的task數(shù)據(jù)
    createTask(task){
        this.state.todos.push({
            task: task,
            isCompleted: false
        })
        this.setState({todos: this.state.todos})
    }
}

App組件中實現(xiàn)了createTask函數(shù)响巢,該函數(shù)綁定到TodoCreate組件中描滔,通過后者的handleCreate事件調(diào)用,并傳遞創(chuàng)建的task內(nèi)容踪古。createTask再把數(shù)據(jù)重新設置state含长,以便渲染整個數(shù)據(jù)變化的組件。之前的handleCreate將改下如下:

todo-create.js
class TodoCreate extends React.Component {
  ...

    handleCreate(event){
        event.preventDefault()
        const task = this.refs.createInput.value
        // 調(diào)用App組件的createTask函數(shù)用于操作todo數(shù)據(jù)
        this.props.createTask(task)
        this.refs.createInput.value = ''
    }
}

Todo 修改

完成了Todo的創(chuàng)建伏穆,應用的功能算是完成了一半拘泞,CURD操作,僅僅是完成了兩步枕扫,還有最重要的修改和刪除兩個功能陪腌。

修改主要針對的是單條todo內(nèi)容的數(shù)據(jù)進行操作,因此大部分邏輯都和TodoListItem組件有關,而基于前面的學習中诗鸭,TodoCreate中的數(shù)據(jù)是需要借助App這個組件進行通信染簇,同樣TodoListItem中遇到數(shù)據(jù)的操作,也需要借助App的組件進行操作强岸,比TodoCreate更復雜的情況是锻弓,TodoListItem的父組件確實TodoList,因此這個數(shù)據(jù)流的傳遞將會被TodoCreate多了一層組件蝌箍。

action 操作

todo的action中的功能青灼,對于todo列表,action將會提供編輯刪除的功能妓盲,一旦點擊了編輯杂拨,將會出現(xiàn)一個表單,同時action將會變成保存取消兩個功能悯衬。一旦點取消弹沽,action將變成之前的樣子。下面先實現(xiàn)這兩組action的交互變化筋粗。

...

class TodoListItem extends React.Component {

    constructor(props){
        super(props)
        // 借助 isEditing state用于存儲修改todo的狀態(tài)
        this.state = {
            isEditing: false
        }
    }

    renderActionSection(){
        
        if(this.state.isEditing){
            return (
                <td>
                    <button>Save</button>
                    <button onClick={this.onCancel.bind(this)}>Cancel</button>
                </td>
            )
        }
        return (
            <td>
                <button onClick={this.onEditing.bind(this)}>Edit</button>
                <button>Delete</button>
            </td>
        )
        
    }

    render() {
        return (
            <tr key={this.props.index}>
                <td>{this.props.todo.task}</td>
                {this.renderActionSection()}
            </tr>
        )
    }

    onEditing(){
        this.setState({
            isEditing: true
        })

    }

    onCancel(){
        this.setState({
            isEditing: false
        })
    }

}

把動態(tài)變化的action內(nèi)容抽出之后贷币,點擊編輯之后,除了action的按鈕變化之外亏狰,還需要將task展示的地方變成一個form表單役纹,以便實際修改task內(nèi)容。因此在展示task內(nèi)容的時候暇唾,需要根據(jù)當前的狀態(tài)(是否是編輯)是否展示表單促脉。

todo-list-item.js

class TodoListItem extends React.Component {
  ...

    renderTaskSection(){

        if (this.state.isEditing){
            return (
                <td>
                    <form>
                        <input type="text" defaultValue={this.props.todo.task} ref="editInput"/>
                    </form>
                </td>
            )
        }
        return <td>{this.props.todo.task}</td> 
    }

    render() {
        return (
            <tr key={this.props.index}>
                {this.renderTaskSection()}
                {this.renderActionSection()}
            </tr>
        )
    }
   ...

todo 編輯

點擊編輯之后,會出現(xiàn)一個可編輯的表單策州,其中defaulValue屬性比較重要瘸味,如果設置value,還需要針對表單的onchange事件進行監(jiān)聽够挂,否則不會修改表單域的內(nèi)容旁仿。

實現(xiàn)todo的編輯功能,通過表單提交來修改內(nèi)容孽糖,我們之前也遇到了創(chuàng)建todo的時候需要提交表單枯冈,兩者的思路類似,都是通過表單的事件办悟,調(diào)用父組件的函數(shù)尘奏,然后更新todo的數(shù)據(jù)狀態(tài),最后重新render數(shù)據(jù)變化的組件病蛉。只不過這一次的函數(shù)還需要通過TodoList這個組件做一次數(shù)據(jù)流向的中繼炫加。

todo-list-item.js

class TodoListItem extends React.Component {
    renderActionSection(){ 
        if(this.state.isEditing){
            return (
                <td>
                    // 綁定save方法
                    <button onClick={this.onSave.bind(this)}>Save</button>
                    <button onClick={this.onCancel.bind(this)}>Cancel</button>
                </td>
            )
        }
       ...
        
    }

    renderTaskSection(){

        if (this.state.isEditing){
            return (
                <td>
                    // 綁定save方法
                    <form onSubmit={this.onSave.bind(this)}>
                        <input type="text" defaultValue={this.props.todo.task} ref="editInput"/>
                    </form>
                </td>
            )
        }
        return <td>{this.props.todo.task}</td> 
    }

    onSave(event){
        event.preventDefault()

        const oldTask = this.props.todo.task
        const newTask = this.refs.editInput.value

        // 調(diào)用父組件的方法
     this.props.saveTask(oldTask, newTask)
        this.setState({
            isEditing: false
        })

    }

下面實現(xiàn)saveTask方法瑰煎,編輯 app.js文件

app.js

class App extends React.Component {

    ...

    render() {
        return (
            <div>
                <h1>React Todo App</h1>
                <TodoCreate createTask={this.createTask.bind(this)}/>
                <TodoList todos={this.state.todos}
                    // 將saveTask函數(shù)傳遞給子組件
                          saveTask={this.saveTask.bind(this)}/>
            </div>
        )
    }
    
  ...
  
    saveTask(oldTask, newTask){
        const foundTask = _.find(this.state.todos, todo => todo.task === oldTask)
        foundTask.task = newTask
        this.setState({todos: this.state.todos})
    }
}

完成了App組件中的saveTask函數(shù)定義,并傳遞給子組件俗孝,此時需要修改TodoList組件酒甸,并將這個函數(shù)方法繼續(xù)傳遞給TodoListItem組件。

todo-list.js

class TodoList extends React.Component {

 renderItem(){
  return  _.map(this.props.todos, (todo, index) => {
   return (
      // 傳遞saveTask函數(shù)方法
    <TodoListItem todo={todo} key={index} saveTask={this.props.saveTask}/>
   )
  })
 }

    ...
}

通過TodoList組件的傳遞赋铝,編輯功能就可以實現(xiàn)了烘挫。下一步,將會實現(xiàn)將todo的狀態(tài)進行改變柬甥,即完成與否的操作功能,點擊todo條目其垄,將變成刪除線苛蒲,表示已經(jīng)完成;重新點擊绿满,將除去刪除線臂外,表示未完成。這是常見的前端toggle操作喇颁。修改TodoListItem組件

todo-list-item.js

class TodoListItem extends React.Component { 
  ... 
    renderTaskSection(){
   ...
   // 增加 taskStyle 和 完成狀態(tài)的刪除線
        if (!this.props.todo.isCompleted){
            return <td onClick={this.onToggle.bind(this)} style={taskStyle}>{this.props.todo.task}</td> 
        }

        return <td onClick={this.onToggle.bind(this)} style={taskStyle}><strike>{this.props.todo.task}</strike></td> 
    }
 
  ... 

    onToggle(){
        const currentTask = this.props.todo.task
        this.props.toggleTask(currentTask)
    }
}

在 renderTaskSection中漏健,如果不是處于編輯狀態(tài),將對todo條目進行綁定一個onToggle的操作橘霎,以及將此時todo的狀態(tài)用style顏色標注蔫浆。style是Jsx中的組件的屬性,本質(zhì)上是一個js對象姐叁,js的對象就是把CSS的編寫改寫一下瓦盛,和JSX組件屬性一樣,遇到連字符連接的屬性外潜,則改未駝峰式書寫原环。

taskStyle = {
  color: this.props.todo.isCompleted ? 'green' : 'red',
  cursor: 'pointer'
}

下面來看onToggle方法,與onSave類似处窥,調(diào)用的都是父級組件傳遞過來的方法操作todo數(shù)據(jù)state然后重新render組件嘱吗。

React props特性

增加了編輯功能之后,還差一個刪除滔驾,todo功能算是完成了谒麦。當然,現(xiàn)在還有兩個小bug哆致,稍后我們再fix弄匕。在此之前,針對TodoListItem組件的數(shù)據(jù)及其狀態(tài)的修改沽瞭,都是調(diào)用父級組件App定義的函數(shù)方法迁匠,其中通過TodoList傳遞,而每一次傳遞,都需要修改TodoList的代碼城丧,這一點實在太繁瑣延曙。為了解決這個問題,可以借助React和ES6的一些特性亡哄。下面修改TodoList組建枝缔。

todo-list.js

...

class TodoList extends React.Component {
    renderItem(){

        // return  _.map(this.props.todos, (todo, index) => {
        //     return (
        //         <TodoListItem todo={todo} key={index} saveTask={this.props.saveTask} toggleTask={this.props.toggleTask}/>
        //     )
        // })
   
   // 將 todo 對象直接傳遞
        return _.map(this.props.todos, (todo, index) => {
            return <TodoListItem key={index} {...todo} />
        })
    }
  ...
}

{...todo}寫法可以把todo({task: task value, isCompleted: isCompleted value})對象傳遞給子組建,相當于給todo對象進行解包蚊惯,等價于task=task valueisCompleted: isCompleted value愿卸。經(jīng)過了這樣處理,原TodoListItem組件中的task獲取就不再是 this.props.todos.task 截型,而是變成了 this.props.task, 相應的isCompleted屬性同理趴荸。即把 this.props.todos 替換成 this.props 即可。

使用 ... 封包和解包的功能是為了減少 props 屬性的傳遞宦焦,之前繁瑣的屬性是各種事件发钝,這些事件包含在 TodoList組件的 this.props 中,因此todo-list.js還需要再修改以便傳遞各種事件波闹。

todo-list.js

class TodoList extends React.Component {

    renderItem(){
       // 除去 this.props 中的 todos屬性酝豪,減少傳遞
        const props = _.omit(this.props, 'todos');
        return _.map(this.props.todos, (todo, index) => {
            return <TodoListItem key={index} {...todo} {...props} />
        })
    }

  ...
}

上述代碼使用了lodash的omit方法,將todos屬性除去精堕,因為map的時候孵淘,會針對當前的item傳遞todo,因此歹篓,不需要把props中的todos傳遞了夺英。通過是用...功能,省去了一大堆props的書寫滋捶。{...props}取代了saveTask={this.props.saveTask} toggleTask={this.props.toggleTask}

刪除&bugfix

刪除功能

todo即將成形痛悯,完成刪除功能和bugfix,再披上css樣式重窟,就大功告成了载萌。實現(xiàn)刪除功能很簡單,基于前面的實踐可知巡扇,再TodoListItem中綁定刪除事件扭仁,然后調(diào)用App的刪除方法即可,同時因為借助了...的解包方式厅翔,不需要再從TodoListItem中顯式的傳遞這個函數(shù)方法啦乖坠。

todo-list-item.js

class TodoListItem extends React.Component {
    ... 

    renderActionSection(){
        ...
        return (
            <td>
                <button onClick={this.onEditing.bind(this)}>Edit</button>
                <button onClick={this.onDelete.bind(this)}>Delete</button>
            </td>
        )        
    }

    onDelete(){
        const currentTask = this.props.task
        this.props.deleteTask(currentTask)
    }
app.js

class App extends React.Component {

    render() {
        return (
            <div>
                ...
                <TodoList todos={this.state.todos}
                          deleteTask={this.deleteTask.bind(this)}
                          toggleTask={this.toggleTask.bind(this)}
                          saveTask={this.saveTask.bind(this)}/>
            </div>
        )
    }

    deleteTask(currentTask){
        _.remove(this.state.todos, todo => todo.task === currentTask)
        this.setState({todos: this.state.todos})
    }
    ...
}

驗證

完成刪除之后,可以嘗試使用啦刀闷。在使用的時候熊泵,會發(fā)現(xiàn)仰迁,即使什么都不輸入,也會增加一條空內(nèi)容的task顽分,同時徐许,相同的task內(nèi)容,在編輯修改的時候卒蘸,總是修改成為第一條內(nèi)容雌隅。產(chǎn)生bug的原因是在查找todo的時候,采用了task內(nèi)容來匹配缸沃,而不是使用一個id之類的唯一標識恰起。

解決的方法也很簡單,在創(chuàng)建和編輯的時候趾牧,禁止發(fā)布空內(nèi)容和相同的內(nèi)容检盼。這修要修改create和save兩個函數(shù)方法,在其中增加一個驗證的函數(shù)即可武氓。


class TodoCreate extends React.Component {

    constructor(props){
        super(props)
        this.state = {
            error: null
        }
    }

    render() {
        return (
            <form onSubmit={this.handleCreate.bind(this)}>
                <input type="text" placeholder="What need I do?" ref="createInput" />
                <button>Create</button>
                {this.renderError()}
            </form>
        )
    }

    handleCreate(event){
        event.preventDefault()
        const task = this.refs.createInput.value

        const error = this.validateInput(task)
        if (error){
            this.setState({error: error})
            return 
        }
        this.props.createTask(task)
        this.refs.createInput.value = ''
    }

    validateInput(task){
        console.log(task)
        if (!task){
            return 'Please enter a task~'
        }else if (_.find(this.props.todos, todo => todo.task === task)){
            return 'Task already exsits!'
        }else{
            return ''
        }
    }

    renderError(){
        if (this.state.error){
            return <p>{this.state.error}</p>
        }
        return null
    }
}

因為用到了與現(xiàn)有的todo對比task內(nèi)容,因此需要從App組件傳給TodoCreate組件仇箱。

app.js

class App extends React.Component {

  ...
  
    render() {
        return (
            <div>
                <h1>React Todo App</h1>
                // 傳遞todos
                <TodoCreate todos={this.state.todos}
                    createTask={this.createTask.bind(this)}/>
                
                <TodoList todos={this.state.todos}
                          deleteTask={this.deleteTask.bind(this)}
                          toggleTask={this.toggleTask.bind(this)}
                          saveTask={this.saveTask.bind(this)}/>
            </div>
        )
    }
  ...

}

增加樣式

與create類似县恕,save的時候,也需要對task的內(nèi)容做驗證剂桥。這里就不再記錄忠烛。具體實現(xiàn)看源碼即可。源碼的實現(xiàn)权逗,把 validateTask方法抽出為公共的方法給TodoCreate 和 TodoListItem使用美尸。

完成了基本功能之后,還需要給app披上一外衣斟薇,在這個看臉的時代师坎,一副好皮囊至關重要。借助與webpack的模塊打包功能堪滨,在react中使用css很簡單胯陋,只需要把css文件當成模塊import即可。例子中使用了siimple的css樣式庫袱箱。

index.js
...

import "siimple.css"
...

源碼

總結

前端發(fā)展迅猛遏乔,之前jQuery一招鮮。隨后backbone发笔,angular等攜帶mvc等理想從后端殺入前端盟萨。一時前端戰(zhàn)場硝煙彌漫,各種框架庫層出不窮了讨。最讓人受不了的是一個工具還沒掌握捻激,就已經(jīng)過時了制轰。與其說前端發(fā)展快,私下覺得是因為前端缺少了太多東西铺罢,才需要工程師把別的端的理念在前端重新實現(xiàn)一遍艇挨。

不管怎么樣,近年來逐漸偏向與react韭赘,angular缩滨,vue幾個項目。在此不想比較它們孰優(yōu)孰劣泉瞻。就個人的感受而言脉漏,也許angular讓你在寫angular,vue也讓你寫vue袖牙,React卻讓你真正的在寫js侧巨,而不是react。通過todo這個應用鞭达,大致可以明白React的基本用法和其核心概念司忱。正如React創(chuàng)作的組件一樣,這些函數(shù)庫react坦仍,redux叨襟,webpack等同樣也是一個個組件,如何搭配合理糊闽,發(fā)揮他們的生態(tài)功力梳玫。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市提澎,隨后出現(xiàn)的幾起案子念链,更是在濱河造成了極大的恐慌虱朵,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件钓账,死亡現(xiàn)場離奇詭異,居然都是意外死亡梆暮,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門偿荷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人忍饰,你說我怎么就攤上這事寺庄。” “怎么了斗塘?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵馍盟,是天一觀的道長。 經(jīng)常有香客問我贞岭,道長,這世上最難降的妖魔是什么话速? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任讲婚,我火速辦了婚禮俊柔,結果婚禮上,老公的妹妹穿的比我還像新娘雏婶。我一直安慰自己,他們只是感情好酵紫,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布错维。 她就那樣靜靜地躺著,像睡著了一般参歹。 火紅的嫁衣襯著肌膚如雪隆判。 梳的紋絲不亂的頭發(fā)上僧界,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天臭挽,我揣著相機與錄音,去河邊找鬼欢峰。 笑死赤赊,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的抛计。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼瘦陈,長吁一口氣:“原來是場噩夢啊……” “哼波俄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起捉貌,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤冬念,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后急前,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡刨摩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年世吨,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片罢浇。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡边篮,死狀恐怖奏甫,靈堂內(nèi)的尸體忽然破棺而出凌受,到底是詐尸還是另有隱情胜蛉,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布领突,位于F島的核電站案怯,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏嘲碱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一恕稠、第九天 我趴在偏房一處隱蔽的房頂上張望扶欣。 院中可真熱鬧料祠,春花似錦、人聲如沸术陶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至巷帝,卻和暖如春楞泼,著一層夾襖步出監(jiān)牢的瞬間笤闯,已是汗流浹背棍厂。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留浦马,地道東北人张漂。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像磺陡,于是被迫代替她去往敵國和親漠畜。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 最近看了一本關于學習方法論的書,強調(diào)了記筆記和堅持的重要性躯喇。這幾天也剛好在學習React,所以我打算每天堅持一篇R...
    gaoer1938閱讀 1,673評論 0 5
  • 翻譯版本,原文請見,第一部分,第二部分 幾周以前,我正在漫無目的的瀏覽Hacker News,讀到一篇關于Redu...
    smartphp閱讀 829評論 1 2
  • 作者:曉冬本文原創(chuàng),轉(zhuǎn)載請注明作者及出處 如今的 Web 前端已被 React正压、Vue 和 Angular 三分天...
    iKcamp閱讀 828評論 0 2
  • 原教程內(nèi)容詳見精益 React 學習指南焦履,這只是我在學習過程中的一些閱讀筆記,個人覺得該教程講解深入淺出郑临,比目前大...
    leonaxiong閱讀 2,833評論 1 18
  • 寧靜的午后躺翻,平淡的心情,拿起一本好書公你,卻發(fā)現(xiàn)自己根本讀不下去,你是否也有這樣的經(jīng)歷呢嚣崭?我也是這樣懦傍,甚至現(xiàn)在也市場出...
    崆山雨閱讀 1,017評論 0 1