一: 寫在文章開頭
今天我們就使用 react
來實現一個簡易版的 todolist
,我們可以使用這個 demo
進行 list
的增刪改差,實際效果如上圖所示。大家可以clone
下來查看:react-todolist
這篇文章我們就不使用 redux
,因爲這個 demo
本身比較簡單,不需要通過 redux
來管理我們的狀態。
redux中也有非常有名的一句話叫做:
"如果你不知道是否需要 Redux,那就是不需要它。"
我們廢話不多說,直接進入正題。
二:項目的目錄結構
.
├── app // 開發的文件夾,組件放在這個文件夾中
│ ├── components // 項目的組件
│ │ ├── App.js // 最外層包含下面組件的總組件
│ │ ├── AppFooter.js // App的三個篩選按鈕的組件
│ │ ├── AppForm.js // 添加list的form
│ │ ├── AppList.js // 顯示list數據的智能組件
│ │ └── AppTodos.js // 顯示list的木偶組件
├── css // 放css文件的地方。
│ ├── semantic.css // 我們的文件用到了semantic.css,
├── node_modules // 第三方的依賴
├── .babelrc // babel配置文件
├── .gitignore // git上傳時忽略的文件
├── bundle.js // webpack build之後的文件
├── index.html // 項目的模版文件
├── main.js // 項目的入口文件
├── webpack.config.js // webpack配置文件
├── README.md // readme文件
└── package.json // 當前整一個項目的依賴
三:前期準備,安裝依賴
1,首先我們新建一個todolist文件夾,根據我的目錄結構建好相應的文件,如果大家嫌麻煩,大家可以clone我的項目,然後看着我的代碼,我會一一進行說明的。package.json我們可以自己創建。
$ mkdir todolist
$ cd todolist
2,建立package.json
文件
npm init
3,安裝相應的依賴,我先解釋一下這些依賴的作用
-
首先安裝Babel,Babel 是一個 JavaScript 編譯器,他可以將es6或者es7的語法轉化爲瀏覽器能識別的javascript。
npm install babel-cli babel-core --save-dev
-
其次安裝我們的主角,
react
npm install react react-dom --save-dev
-
安裝
webpack
,打包工具;和webpack-dev-server
,用於來給我們開啓一個服務的。npm install webpack webpack-dev-server --save-dev
-
安裝
loader
打包,通過使用不同的loader
,webpack
有能力調用外部的腳本或工具,實現對不同格式的文件的處理,比如說分析轉換scss爲css,或者把下一代的JS文件(ES6,ES7)轉換爲現代瀏覽器兼容的JS文件,對React的開發而言,合適的Loaders可以把React的中用到的JSX文件轉換爲JS文件。大家想了解更多的webpack的內容,可以參考webpack中文文檔
npm install css-loader babel-loader style-loader --save-dev
然後我們在
webpack.config.js
中引用這些依賴,具體的格參數的意思,大家可以參考webpack各文檔說明,我下面也會簡單的註釋一下。module.exports = { entry: './main.js', // webpack打包的入口文件 output: { filename: './bundle.js' // 輸出之後的文件名 }, module: { loaders: [ { test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader' // babel的loader,jsx文件使用babel-loader處理 }, { test: /\.css$/, exclude: /node_modules/, loader: 'style!css' // css和styleloader,對css後綴的文件進行處理 } ] }, devtool: 'cheap-source-map', }
-
同時要讓我們的
babel
能在react
中生效,同時支持es6
,我們需要安裝下面的插件npm install babel-preset-es2015 babel-preset-react babel-preset-stage-0 --save-dev
安裝完依賴後,我們在
.babelrc
文件中引入這幾個依賴{ "presets": ["es2015","react",'stage-0'] }
-
其次爲了當我們每次添加list的時候有一個唯一的id,我們使用
uuid
npm install uuid --save-dev
四:組件的編寫,是我們的頁面能夠顯示出來
-
編寫模版文件
index.html
在這個模版文件裏面,我們引入
semantic.css
文件,然後建立一個id=app
的<div>
爲了我們後續的react
操作。<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>react-todolist</title> <link rel="stylesheet" type="text/css" href="/css/semantic.css"> <style> .active { color: red } .line{ display: inline-block; border-bottom: 1px solid #222222; width:100px; position: absolute; left:0; top:7px; } .ui.comments .comment{ padding:0; margin:2em 0 0; } </style> </head> <body> <div class="ui container" style="padding:30px;"> <div id="app"></div> </div> <script src="./bundle.js"></script> </body> </html>
-
編寫入口文件
main.js
這邊的
data
是我們的模擬數據,將其傳入到<App/>
組件,在子組件中可以通過props.data
的方法獲取data
。對於react的基礎知識,大家可以參考來自一位react新手的react入門須知。import React from 'react' import ReactDOM from 'react-dom' import App from './app/component/App' let data = [ {id: 0, text: '天氣不錯哦!!!', complete: false}, {id: 1, text: '天氣不錯哦!!!', complete: false}, {id: 2, text: '出去玩啊!!!', complete: true}, ] ReactDOM.render( <App data={data}/>, document.getElementById('app') )
-
編寫
component
裏面的組件-
App.js
這個組件我們可以認爲是一個容器組件,我們會把
AppForm
、AppList
、AppFooter
放在這個組件中。import React from 'react' import AppList from './AppList.js' import AppForm from './AppForm.js' import AppFooter from './AppFooter.js' class App extends React.Component { state = { choosevalue : 1, data: this.props.data } render () { const { data } = this.state; return ( <div className='ui comments'> <h1>My Todo with React</h1> <div className='ui divider'></div> <AppForm /> <AppList data={data}/> <AppFooter /> </div> ) } } export default App;
-
AppForm.js
這個組件是我們添加
list
用的一個form
組件,其中下面的styles
這個對象那個也是jsx
中申明樣式的一種方式,我們還可以使用className
來添加樣式名字。import React from 'react'; import uuid from 'uuid'; var styles = { 'title': { width: 200, display: 'inline-block', marginRight: 10, verticalAlign: 'top' } } class AppForm extends React.Component { render () { return ( <form className='ui reply form'> <div className='field' style={styles.title}> <input type='text' placeholder='TODO' ref='text' /> </div> <button type='submit' className='ui blue button'> 添加 </button> </form> ) } } export default AppForm;
-
AppList.js
這個組件是我們在
react
中常說的智能組件,得到數據lists
後通過map
方法遍歷數據,然後進行渲染。這裏的map
方法是用到了es6
中的解構賦值,大家可以參考react新手必須懂得es6的基礎知識,然後將值一一傳遞到子組件中去。import React from 'react' import AppTodos from './AppTodos' class AppList extends React.Component { render () { const a = this.props.data.map(({ id, text, complete }, index) => { return <AppTodos key={index} id={id} text={text} complete={complete} /> }) return ( <div> { a } </div> ) } } export default AppList;
-
AppTodos.js
這個組件是我們在
react
中常說的木偶組件,就是得到數據渲染組件。import React from 'react' var styles = { 'title': { paddingLeft: '20px', paddingRight: '50px', position: 'relative' }, 'delete': { marginLeft: '20px', marginRight: '50px' } } class AppTodos extends React.Component { render () { return ( <div className='comment'> <div className='content'> <span className='author' style={styles.title} > {this.props.text} <span className={this.props.complete ? 'line' : ''} /> </span> <span className='author' style={styles.title}> {this.props.complete ? '已完成' : '未完成'} </span> <span className='author'>{this.props.id}</span> <span className='ui blue button' style={styles.delete} > 刪除 </span> </div> </div> ) } } export default AppTodos;
-
AppFooter.js
這個組件就是下面的三個按鈕
全部
、未完成
、已完成
。import React from 'react' var styles = { 'title': { marginRight: 10, fontSize: 20 }, 'top': { marginTop: 20 } } class AppFooter extends React.Component { render () { return ( <div> <h2 style={styles.top}>show</h2> <button type='submit' style={styles.top} className='ui blue button' value='1' ref='all' > 全部 </button> <button type='submit' style={styles.top} className='ui blue button' value='2' ref='active' > 還未完成 </button> <button type='submit' style={styles.top} className='ui blue button' value='3' ref='complete' > 已完成 </button> </div> ) } } export default AppFooter;
然後我們在命令行輸入,會開啓一個服務。
npm run server
打開瀏覽器,輸入
http://localhost:8080
,可看到: -
5,實現list
的添加操作
-
首先理一下流程
首先在
form
輸入待辦事情,點擊添加觸發一個handleSubmit
點擊函數,但是我們的data
是通過<App />
組件來分發的,而list
是組件AppList
渲染的。這裏涉及到了從子組件傳遞值給父組件
,其實也很簡單,就從父組件中傳一個函數給子組件,子組件將值通過函數再傳遞出去,大家可以參考react父子組件間的交流。 -
在組件
App.js
中,我們加入一個OnAddTodoItem
函數,並傳入到AppForm
組件中,我們新建函數中將傳進來的newItem
通過concat()
現在的data
,然後更新state
。... OnAddTodoItem (newItem) { let newdata = this.state.data.concat(newItem); this.setState({data : newdata}); } render () { const { data } = this.state; return ( <div className='ui comments'> <h1>My Todo with React</h1> <div className='ui divider'></div> <AppForm AddTodoItem={this.OnAddTodoItem.bind(this)} /> <AppList data={data}/> <AppFooter /> </div> ) } } export default App;
-
在組件
AppForm.js
中,我們加入一個handleSubmit
函數,並在form
表單添加一個onClick
函數,將用戶輸入的數據,通過uuid生成的id
、輸入的text
、以及是否完成false
。通過函數傳遞給父組件。... handleSubmit (event) { event.preventDefault() let text = this.refs.text.value if (!text.trim()) { alert("Input can't be null") return } let id = uuid(); this.props.AddTodoItem({id,text,complete:false}); } render () { return ( <form className='ui reply form' onSubmit={this.handleSubmit.bind(this)}> <div className='field' style={styles.title}> <input type='text' placeholder='TODO' ref='text' /> </div> <button type='submit' className='ui blue button'> 添加 </button> </form> ) } } export default AppForm;
你可以看到如下效果:
6,完成篩選功能
-
首先裏一下流程
我們給下面的三個按鈕設置了不同的
value
,1
代表全部、2
代表未完成、3
代表已完成,然後我們根據相應的value
,展示相應的list,給三個按鈕分別加上handleAll
、handleActive
、handleComplete
三個方法,在onClick
時觸發。 -
App.js
添加一個加上
choosevalue
的state
,默認爲1
,即全選,同時將其傳入到<AppList/>
中去,同時添加chooseValue
的方法,然後傳入到AppFooter
組件中去。... state = { choosevalue : 1, data: this.props.data } ChooseValue (id) { this.setState({choosevalue:id}); } ... <AppList data={this.state.data} choosevalue={this.state.choosevalue} /> <AppFooter SubmitChooseValue={this.ChooseValue.bind(this)} />
-
AppFooter.js
... handleAll () { let all = this.refs.all.value; this.props.SubmitChooseValue(all); } handleActive () { let active = this.refs.active.value; this.props.SubmitChooseValue(active); } handleComplete () { let complete = this.refs.complete.value this.props.SubmitChooseValue(complete); } render () { return ( <div> <h2 style={styles.top}>show</h2> <button type='submit' style={styles.top} className='ui blue button' value='1' ref='all' onClick={this.handleAll.bind(this)} > 全部 </button> <button type='submit' style={styles.top} className='ui blue button' value='2' ref='active' onClick={this.handleActive.bind(this)} > 還未完成 </button> <button type='submit' style={styles.top} className='ui blue button' value='3' ref='complete' onClick={this.handleComplete.bind(this)} > 已完成 </button> </div> ) } } export