react新手demo——TodoList
一: 写在文章开头
今天我们就使用 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
打开浏览器,输入
react-demo_1.pnghttp://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;
你可以看到如下效果:
react-add2.gif
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 default AppFooter;
-
AppList.js
根据
value
渲染相应的List
... class AppList extends React.Component { render () { var value = this.props.choosevalue; const a = this.props.data.map(({ id, text, complete }, index) => { if (value == '1') { return <AppTodos key={index} id={id} text={text} complete={complete} /> } if (value == '2' && complete === false) { return <AppTodos key={index} id={id} text={text} complete={complete} /> } if (value == '3' && complete === true) { return <AppTodos key={index} id={id} text={text} complete={complete} /> } }) return ( <div> { a } </div> ) } } export default AppList;
你可以看到如下效果:
react-add3.gif7,修改list
状态和删除list
的功能
-
首先里一下流程
修改
list
的状态,其实就是我们找到相应id
的list
,然后将其的complete
改为true
就可以了。删除
list
时,就是我们找到相应id
的list
,我们给他添加一个deleteFlag
属性,在AppList
渲染的时候,我门判断是否有deleteFlag
来惊醒渲染。 -
App.js
添加两个方法
AllChangeComplete
:改变状态;AllOnDeleteItem
:删除list
的方法。然后通过组件传入到相应的子组件。... AllChangeComplete (id) { let newdata = this.state.data.map((item,index) => { if(item.id === id) { item.complete = !item.complete; } return item; }) this.setState({data : newdata}); } AllOnDeleteItem (id) { console.log(id); let newdata = this.state.data.map(function (item) { console.log(item); if (item.id == id) { item.deleteFlag = true } return item }) this.setState({ data: newdata }) } ... <AppList data={this.state.data} choosevalue={this.state.choosevalue} ChangeCompleteTop={this.AllChangeComplete.bind(this)} DeleteItemTop={this.AllOnDeleteItem.bind(this)} /> ...
-
AppList.js
我们根据
complete
与deleteFlag
来进行渲染。并在这个组件中,充当一个中间的过度组件,将AppTodos
触发的函数传到App.js
中去改变状态。... class AppList extends React.Component { SubmitDelete (id) { this.props.DeleteItemTop(id) } ChangeDone (id) { this.props.ChangeCompleteTop(id); } render () { var value = this.props.choosevalue; const a = this.props.data.map(({ id, text, complete }, index) => { if (value == '1' && deleteFlag !== true) { return <AppTodos key={index} id={id} text={text} complete={complete} ChangeCompleteItem={this.ChangeDone.bind(this)} DeleteItem={this.SubmitDelete.bind(this)} /> } if (value == '2' && complete === false && deleteFlag !== true) { return <AppTodos key={index} id={id} text={text} complete={complete} ChangeCompleteItem={this.ChangeDone.bind(this)} DeleteItem={this.SubmitDelete.bind(this)} /> } if (value == '3' && complete === true && deleteFlag !== true) { return <AppTodos key={index} id={id} text={text} complete={complete} ChangeCompleteItem={this.ChangeDone.bind(this)} DeleteItem={this.SubmitDelete.bind(this)} /> } }) return ( <div> { a } </div> ) } } export default AppList;
-
AppTodos.js
添加两个方法,
handleChangeComplete
和handleDelete
。一个时更改状态,一个是删除。... handleChangeComplete () { let newComplete = this.props; this.props.ChangeCompleteItem(this.props.id); } handleDelete () { this.props.DeleteItem(this.props.id); } render () { return ( <div className='comment'> <div className='content'> <span className='author' style={styles.title} onClick={this.handleChangeComplete.bind(this)}> {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} onClick={this.handleDelete.bind(this)}> 删除 </span> </div> </div> ) } } export default AppTodos;
至此我们完成了所有的功能,如下图所示:
react-add4.gif8,结语
我们这次没有借助任何的状态管理工具,如redux
来做这个demo
。因为大家做下来也能发现,其实我们这个demo
的层级只有2层,所以我们可以通过props
来进行父子间的通信。
但是其实我们也发现了,如果当组件的层级越来越多的时候,我们通过这样来进行父子间的通信就不方便了,在这个时候我们就需要用到redux
或者mobx
等等的状态管理工具。
其实这边的删除和修改list状态我都是在前端模拟处理的,在实际工作中我们都会通过接口去处理,然后根据返回值进行更改state
。
希望这篇文章对大家有一点启发,有任何问题可以在简书里私信我哦!
来自一个奔跑在前端路上的前端小白。