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)選擇所安裝的依賴愧怜。首先需要安裝webpack
和webpack-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;
props
和state
是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 value
和 isCompleted: 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)功力梳玫。