react新手demo——TodoList

作者: darrell | 来源:发表于2018-02-12 01:29 被阅读258次
    react-todolist.gif

    一: 写在文章开头

    今天我们就使用 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打包,通过使用不同的loaderwebpack有能力调用外部的脚本或工具,实现对不同格式的文件的处理,比如说分析转换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里面的组件

      1. App.js

        这个组件我们可以认为是一个容器组件,我们会把AppFormAppListAppFooter放在这个组件中。

        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;
        
      2. 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;
        
      3. 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;
        
      4. 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;
        
      5. 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,可看到:

      react-demo_1.png

    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,完成筛选功能

    • 首先里一下流程

      我们给下面的三个按钮设置了不同的value1代表全部、2代表未完成、3代表已完成,然后我们根据相应的value,展示相应的list,给三个按钮分别加上handleAllhandleActivehandleComplete三个方法,在onClick时触发。

    • App.js

      添加一个加上choosevaluestate,默认为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.gif

    7,修改list状态和删除list的功能

    • 首先里一下流程

      修改list的状态,其实就是我们找到相应idlist,然后将其的complete改为true就可以了。

      删除list时,就是我们找到相应idlist,我们给他添加一个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

      我们根据completedeleteFlag来进行渲染。并在这个组件中,充当一个中间的过度组件,将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

      添加两个方法,handleChangeCompletehandleDelete。一个时更改状态,一个是删除。

      ...
      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.gif

    8,结语

    我们这次没有借助任何的状态管理工具,如redux来做这个demo。因为大家做下来也能发现,其实我们这个demo的层级只有2层,所以我们可以通过props来进行父子间的通信。

    但是其实我们也发现了,如果当组件的层级越来越多的时候,我们通过这样来进行父子间的通信就不方便了,在这个时候我们就需要用到redux或者mobx等等的状态管理工具。

    其实这边的删除和修改list状态我都是在前端模拟处理的,在实际工作中我们都会通过接口去处理,然后根据返回值进行更改state

    希望这篇文章对大家有一点启发,有任何问题可以在简书里私信我哦!

    来自一个奔跑在前端路上的前端小白。


    9,参考资料

    相关文章

      网友评论

        本文标题:react新手demo——TodoList

        本文链接:https://www.haomeiwen.com/subject/jqfrtftx.html