亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

React-Todos

系統(tǒng) 1803 0

最近學(xué)完React的最基本概念,閑下來(lái)的時(shí)候就自己寫了一個(gè)Todo-List的小應(yīng)用。這里做個(gè)簡(jiǎn)略的說(shuō)明,給想好好學(xué)React的新手看。

React-Todo


學(xué)習(xí)前提

這里我用了webpackb做了babel和JSX預(yù)處理和模塊打包。所以對(duì)React和一些ES2015(ES6)的語(yǔ)法要有一定的了解。我相信學(xué)習(xí)ES2015絕對(duì)是劃算的,因?yàn)樗荍s的規(guī)范。這里給出學(xué)習(xí)的地方,阮一峰老師的 ECMAScript 6 入門 或者babel的相關(guān)文檔 Learn ES2015

功能需求

最后的實(shí)際效果:

React-Todos_第1張圖片

我們需要做到的功能有:

  • 可以在最上面的input里,使用回車來(lái)添加任務(wù)。
  • 在中間的任務(wù)列表里,由checkbox來(lái)控制任務(wù)的狀態(tài)。
  • 已完成的任務(wù)有一個(gè) line-through 的樣式。
  • 當(dāng)鼠標(biāo)移到每一個(gè)任務(wù)時(shí),都會(huì)出現(xiàn)刪除按鈕提供刪除。
  • 在底部有一個(gè)全選按鈕,用于控制所有的任務(wù)狀態(tài)。
  • 還有已完成與總數(shù)的顯示。
  • 可以清空已完成的任務(wù)。

項(xiàng)目下載

上面就是一個(gè)Todo-List最基本的功能,而我們這次就是用React實(shí)現(xiàn)上述功能。例子在我的github上可以download下來(lái),可以用作參考: React-Todos

正式開(kāi)始


加載npm模塊

重要要開(kāi)始我們的React-Todo的項(xiàng)目了,首先我們就要新建項(xiàng)目,通過(guò)npm我們可以很輕松的創(chuàng)建項(xiàng)目,并加載我們所需要的各個(gè)組件。大家可以在自己的項(xiàng)目里,用我的 package.json 去加載所需要的模塊。通過(guò)命令行進(jìn)行安裝。

    
      $ npm install
    
  

這里提一下,因?yàn)槲覀冞@里僅僅是前端靜態(tài)的,并不涉及到數(shù)據(jù)庫(kù)。所以我自己寫了一個(gè)非常簡(jiǎn)單的用于操作localStorage的小模塊 localDb 。所以涉及到數(shù)據(jù)存儲(chǔ)的時(shí)候,都是用localStorage來(lái)代替數(shù)據(jù)庫(kù)。它的原理就是,通過(guò)將數(shù)據(jù)格式化成JSON字符串進(jìn)行存儲(chǔ),使用的時(shí)候就解析JSON字符串。這個(gè)模塊在我的github的例子里有,需要從那里復(fù)制一份來(lái),放在node_modules的文件夾內(nèi)。

配置webpack

經(jīng)過(guò)一輪漫長(zhǎng)的等待,我們終于安裝好所需要的各個(gè)模塊了。我們最開(kāi)始我們的react的編碼前,需要對(duì)webpack進(jìn)行配置。關(guān)于webpack的學(xué)習(xí),我這里就不贅述了,在前一篇?jiǎng)傊v完。下面直接看一看 webpack.config.js

    
      // webpack.config.js
var path = require('path');

module.exports = {
    entry: "./src/entry.js",
    output: {
        path: path.join(__dirname, 'out'),
        publicPath: './out/',
        filename: "bundle.js"
    },
    externals: {
        'react': 'React'
    },
    module: {
        loaders: [
            { test: /\.js$/, loader: "jsx!babel", include: /src/},
            { test: /\.css$/, loader: "style!css"},
            { test: /\.scss$/, loader: "style!css!sass"},
            { test: /\.(jpg|png)$/, loader: "url?limit=8192"}
        ]
    }
};
    
  

這里一切從簡(jiǎn),可以看到入口文件是在src文件夾里的 entry.js ,然后輸出文件放在out文件夾的 bundle.js 里。

配置一下模塊的loaders,先用babel-loader再用jsx-loader。這樣子我們就可以讓ES6配合JSX編寫我們的React組件了。其它的加載器也沒(méi)什么好說(shuō)的了,如果不清楚可以翻我上一篇關(guān)于webpack的文章。

這里提一下externals屬性,這個(gè)屬性是告訴webpack當(dāng)遇到 require('react') 的時(shí)候,不去處理并且默認(rèn)為全局的React變量。這樣子,我們就需要在 index.html 單獨(dú)用src去加載js。

分析各個(gè)組件

App組件

我這里并不會(huì)教大家手把手將這個(gè)React-Todo做出來(lái),但是可以結(jié)合例子進(jìn)行分析理解。先來(lái)看看總的組件,也就是App。

    
      import React from "react";
import LocalDb from "localDb";

import TodoHeader from "./TodoHeader.js";
import TodoMain from "./TodoMain.js";
import TodoFooter from "./TodoFooter.js";

class App extends React.Component {
    constructor(){
        super();
        this.db = new LocalDb('React-Todos');
        this.state = {
            todos: this.db.get("todos") || [],
            isAllChecked: false
        };
    }

    // 判斷是否所有任務(wù)的狀態(tài)都完成,同步底部的全選框
    allChecked(){
        let isAllChecked = false;
        if(this.state.todos.every((todo)=> todo.isDone)){
            isAllChecked = true;
        }
        this.setState({todos: this.state.todos, isAllChecked});
    }

    // 添加任務(wù),是傳遞給Header組件的方法
    addTodo(todoItem){
        this.state.todos.push(todoItem);
        this.allChecked();
        this.db.set('todos',this.state.todos);
    }

    // 改變?nèi)蝿?wù)狀態(tài),傳遞給TodoItem和Footer組件的方法
    changeTodoState(index, isDone, isChangeAll=false){
        if(isChangeAll){
            this.setState({
                todos: this.state.todos.map((todo) => {
                    todo.isDone = isDone;
                    return todo;
                }),
                isAllChecked: isDone
            })
        }else{
            this.state.todos[index].isDone = isDone;
            this.allChecked();
        }
        this.db.set('todos', this.state.todos);
    }

    // 清除已完成的任務(wù),傳遞給Footer組件的方法
    clearDone(){
        let todos = this.state.todos.filter(todo => !todo.isDone);
        this.setState({
            todos: todos,
            isAllChecked: false
        });
        this.db.set('todos', todos);
    }

    // 刪除當(dāng)前的任務(wù),傳遞給TodoItem的方法
    deleteTodo(index){
        this.state.todos.splice(index, 1);
        this.setState({todos: this.state.todos});
        this.db.set('todos', this.state.todos);
    }

    render(){
        var props = {
            todoCount: this.state.todos.length || 0,
            todoDoneCount: (this.state.todos && this.state.todos.filter((todo)=>todo.isDone)).length || 0
        };
        return (
            <div className="panel">
                <TodoHeader addTodo={this.addTodo.bind(this)}/>
                <TodoMain deleteTodo={this.deleteTodo.bind(this)} todos={this.state.todos} changeTodoState={this.changeTodoState.bind(this)}/>
                <TodoFooter isAllChecked={this.state.isAllChecked} clearDone={this.clearDone.bind(this)} {...props} changeTodoState={this.changeTodoState.bind(this)}/>
            </div>
        )
    }
}
React.render(<App/>, document.getElementById("app"));
    
  

用ES6寫React最大的不同就是,組件可以通過(guò)繼承React.Components來(lái)得到,并且初始化state也不需要冗長(zhǎng)的getInitalialState,直接在構(gòu)造函數(shù)里操作this.state即可。更優(yōu)秀的便是 ... spread擴(kuò)展操作符,可以讓我們省下一堆不必要的代碼,這個(gè)接下來(lái)再說(shuō)。

App狀態(tài)state

我們知道React的主流思想就是,所有的state狀態(tài)和方法都是由父組件控制,然后通過(guò)props傳遞給子組件,形成一個(gè)單方向的數(shù)據(jù)鏈路,保持各組件的狀態(tài)一致。所以我們?cè)谶@個(gè)父組件App上,看的東西稍微有點(diǎn)多。一點(diǎn)點(diǎn)來(lái)看:

    
      constructor(){
    super();
    this.db = new LocalDb('React-Todos');
    this.state = {
        todos: this.db.get("todos") || [],
        isAllChecked: false
    };
}
    
  

在App組件的constructor內(nèi),我們先是初始化了我們的localStorage的數(shù)據(jù)庫(kù),放在了this.db上。然后便是初始化了state,分別有兩個(gè),一個(gè)是todos的列表,一個(gè)是所有的todos是否全選的狀態(tài)。

App方法

    
      // 判斷是否所有任務(wù)的狀態(tài)都完成,同步底部的全選框
allChecked()

// 添加一個(gè)任務(wù),參數(shù)是一個(gè)todoItem的object
addTodo(todoItem)

// 改變?nèi)蝿?wù)的狀態(tài),index是第幾個(gè),isDone是狀態(tài),isChangeAll是控制全部狀態(tài)的
changeTodoState(index, isDone, isChangeAll=false) // 參數(shù)默認(rèn)位false

// 清空已完成
clearDone()

// 刪除面板上第幾個(gè)任務(wù)
deleteTodo(index)

// react用于渲染的函數(shù)
render(){
	<div className="panel">
		<TodoHeader />
		<TodoMain />
		<TodoFooter />
	</div>
}
    
  

我們可以從render函數(shù)看到整個(gè)組件的結(jié)構(gòu),可以看到其實(shí)結(jié)構(gòu)非常簡(jiǎn)單,就是上中下。上面的TodoHeader自然就是用來(lái)輸入任務(wù)的地方,中間就是展示并操作todo-list的,而底部就是顯示數(shù)據(jù)并提供特殊操作。這里還是要提醒一句,所有標(biāo)簽都必須閉合,即使是非結(jié)對(duì)的,也要用斜杠閉合上。

    
      	render(){
        var props = {
            todoCount: this.state.todos.length || 0,
            todoDoneCount: (this.state.todos && this.state.todos.filter((todo)=>todo.isDone)).length || 0
        };
        return (
            <div className="panel">
                <TodoHeader addTodo={this.addTodo.bind(this)}/>
                <TodoMain deleteTodo={this.deleteTodo.bind(this)} todos={this.state.todos} changeTodoState={this.changeTodoState.bind(this)}/>
                <TodoFooter isAllChecked={this.state.isAllChecked} clearDone={this.clearDone.bind(this)} {...props} changeTodoState={this.changeTodoState.bind(this)}/>
            </div>
        )
    }
    
  

我們可以看到,其他的方法都是傳到子組件上,就不一一詳細(xì)說(shuō)如何實(shí)現(xiàn)的了。總體的思想就是,方法在父組件定義,通過(guò)props傳給需要的子組件進(jìn)行調(diào)用傳參,最后返回到父組件上執(zhí)行函數(shù),存儲(chǔ)數(shù)據(jù)、改變state和重新render。方法需要 bind(this) ,不然方法內(nèi)部的this指向會(huì)不正確。

計(jì)算需要的數(shù)據(jù)后,通過(guò)props傳遞到子組件。如果細(xì)心的同學(xué)應(yīng)該可以看到像這樣的 {...props} ,這就是我之前說(shuō)過(guò)的spread操作符。如果我們沒(méi)有用這個(gè)操作符,就要這樣寫:

    
      <TodoFooter {...props} /> // spread操作符
<TodoFooter todoCount={props.todoCount} todoDoneCount={props.todoDoneCount} />
    
  

最佳的實(shí)踐就是,當(dāng)父組件傳props給子組件,然后子組件要將props轉(zhuǎn)發(fā)給孫子組件的時(shí)候,spread操作符簡(jiǎn)直讓人愉悅!可以對(duì)一堆麻煩又丑又長(zhǎng)的代碼可以say goodbye了!

最后我們將整個(gè)App渲染到DOM上即可。

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

AppHeader組件

    
      import React from "react";

class TodoHeader extends React.Component {

    // 綁定鍵盤回車事件,添加新任務(wù)
    handlerKeyUp(event){
        if(event.keyCode === 13){
            let value = event.target.value;

            if(!value) return false;

            let newTodoItem = {
                text: value,
                isDone: false
            };
            event.target.value = "";
            this.props.addTodo(newTodoItem);
        }
    }

    render(){
        return (
            <div className="panel-header">
                <input onKeyUp={this.handlerKeyUp.bind(this)} type="text" placeholder="what's your task ?"/>
            </div>
        )
    }
}

export default TodoHeader;
    
  

到了子組件,方法就沒(méi)那么多了,一般子組件就是綁定事件。可以看到在子組件綁定了keyUp事件,用來(lái)確定回車鍵并調(diào)用父組件傳來(lái)的 addTodo() ,將新生成的todo任務(wù)作為參數(shù)傳入。

AppFooter組件

    
      import React from "react";
export default class TodoFooter extends React.Component{

    // 處理全選與全不選的狀態(tài)
    handlerAllState(event){
        this.props.changeTodoState(null, event.target.checked, true);
    }

    // 綁定點(diǎn)擊事件,清除已完成
    handlerClick(){
        this.props.clearDone();
    }

    render(){
        return (
            <div className="clearfix todo-footer">
                <input checked={this.props.isAllChecked} onChange={this.handlerAllState.bind(this)} type="checkbox" className="fl"/>
                <span className="fl">{this.props.todoDoneCount}已完成 / {this.props.todoCount}總數(shù)</span>
                <button onClick={this.handlerClick.bind(this)} className="fr">清除已完成</button>
            </div>
        )
    }
}
    
  

我們先來(lái)看看這個(gè)footer上有哪些方法。第一個(gè)就是處理todo狀態(tài)的,它通過(guò)底部的checkbox的change事件觸發(fā)。然后就是清空已完成的按鈕的點(diǎn)擊事件的方法 handlerClick() 。然后下面的數(shù)據(jù)顯示,就通過(guò)props的值進(jìn)行顯示。

TodoMain

    
      import React from "react";
import TodoItem from "./TodoItem.js"

export default class TodoMain extends React.Component{
    // 遍歷顯示任務(wù),轉(zhuǎn)發(fā)props
    render(){
        return (
            <ul className="todo-list">
                {this.props.todos.map((todo, index) => {
                    return <TodoItem key={index} {...todo} index={index} {...this.props}/>
                })}
            </ul>
        )
    }
}
    
  

Main組件的作用就是,將props傳過(guò)來(lái)的todos遍歷顯示出來(lái)。所以對(duì)每一個(gè)todo的細(xì)致操作都是放在TodoItem上。

TodoItem

    
      import React from "react";
export default class TodoItem extends React.Component{

    // 處理任務(wù)是否完成狀態(tài)
    handlerChange(){
        let isDone = !this.props.isDone;
        this.props.changeTodoState(this.props.index, isDone);
    }

    // 鼠標(biāo)移入
    handlerMouseOver(){
        React.findDOMNode(this.refs.deleteBtn).style.display = "inline";
    }

    // 鼠標(biāo)移出
    handlerMouseOut(){
        React.findDOMNode(this.refs.deleteBtn).style.display = "none";
    }

    // 刪除當(dāng)前任務(wù)
    handlerDelete(){
        this.props.deleteTodo(this.props.index);
    }

    render(){
        let doneStyle = this.props.isDone ? {textDecoration: 'line-through'} : {textDecoration: 'none'};

        return (
            <li
                onMouseOver={this.handlerMouseOver.bind(this)}
                onMouseOut={this.handlerMouseOut.bind(this)}
            >
                <input type="checkbox" checked={this.props.isDone} onChange={this.handlerChange.bind(this)}/>
                <span style={doneStyle}>{this.props.text}</span>
                <button style={{'display': 'none'}} ref="deleteBtn" onClick={this.handlerDelete.bind(this)}  className="fr">刪除</button>
            </li>
        )
    }
}
    
  

在TodoItem主要處理多個(gè)交互,包括修改任務(wù)狀態(tài),刪除任務(wù)。還有就是鼠標(biāo)移到相應(yīng)的任務(wù)上才顯示刪除按鈕。

我們可以看到 render() 函數(shù),是控制了任務(wù)的樣式。標(biāo)簽內(nèi)的style是需要接受一個(gè)對(duì)象的,所以所有的CSS屬性名,都要變成駝峰形的。

總結(jié)


其實(shí)真正的回過(guò)頭看React-Todos,會(huì)覺(jué)得React帶給我們的組件化的思想用起來(lái)太舒服了。我們通過(guò)父組件來(lái)控制狀態(tài),并通過(guò)props傳遞,來(lái)保證組件內(nèi)的狀態(tài)一致。我們可以非常有效的維護(hù)我們的交互代碼,因?yàn)槲覀円谎劬椭溃@個(gè)事件屬于哪個(gè)組件管理。它的模型其實(shí)非常輕,只有View層,但是它帶給我們?nèi)碌臅?shū)寫前端組件的方法是非常好的,我個(gè)人認(rèn)為如果未來(lái)的站點(diǎn)交互性愈來(lái)愈多,React是很有可能代替JQuery成為必備的技能。

React-Todos


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長(zhǎng)會(huì)非常 感謝您的哦!!!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 成人嫩草研究院永久网址 | 天天想天天干 | 国产精品爱久久久久久久 | 美女视频黄是免费的 | 国农村精品国产自线拍 | 四虎在线视频观看大全影视 | 国产精品久久亚洲一区二区 | 久草视频免费在线观看 | 亚洲小视频在线播放 | 国产精品第二页在线播放 | 五月久久亚洲七七综合中文网 | 天天夜夜爽 | 日本一级特黄aa毛片免费观看 | 日日lu| 91精品国产免费久久 | 日本h片a毛片在线播放 | 天天想天天干 | 免费看欧美一级特黄a毛片 免费看欧美一级特黄α大片 | 99热7| 久久久久国产精品免费网站 | 国产日韩欧美精品在线 | 日韩美女一区 | 精品久久久久久久99热 | 99热com| 久久国产精品夜色 | 色片网| 91在线视| 久草在线免费资源 | 色婷婷在线播放 | 中文国产成人精品久久一 | 99久热re在线精品视频 | h片在线播放免费高清 | 香蕉视频看片 | 成熟女人免费一级毛片 | 兔子bt资源在线 | 深夜福利免费在线观看 | 国产欧美国产精品第一区 | 福利影院第一页 | 欧美真人毛片动作视频 | 波多野结衣免费播放 | 综合爱爱网|