14. 完善第二个react项目(bulma,prop-type

2018-11-07  本文已影响0人  dwy_interesting

在原有项目基础上实现:

  1. enter键提交输入框内容
  2. 点击添加键后聚焦输入框
  3. 实现删除任务
  4. 实现任务状态切换
  5. 实现任务数目统计
  6. bulma界面美化
  7. classnames判断条件添加className
  8. prop-types数据检测

在项目目录下安装bulma,prop-types,classnames
命令:cnpm install bulma prop-types classnames --save
在项目public目录下的index.html文件中引入bulma的css

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.2/css/bulma.min.css">

代码:
在src目录下:
index.js

import React from 'react';
import ReactDom from 'react-dom';
import TodoApp from './TodoApp';
//渲染TodoApp内的内容到id位root的选择器
ReactDom.render(
    <TodoApp />,
    document.querySelector("#root")
)

TodoApp.js

import TodoHeader from './component/TodoHeader';//引入TodoHeader组件
import TodoInput from './component/TodoInput';//引入TodoInput组件
import TodoList from './component/TodoList';//引入TodoList组件
import TodoFooter from './component/TodoFooter';//引入TodoFooter组件
import React, { Component } from 'react'
//TodoApp组件
export default class TodoApp extends Component {
  //定义state存储list中要显示的数据
  constructor(){
    super();
    this.state = {
      todos: [{
        id: Math.random(),
        text: '哈哈',
        iscompleted: true
      }, {
        id: Math.random(),
        text: '大大',
        iscompleted: false
      }, {
        id: Math.random(),
        text: '小小',
        iscompleted: true
      }]
    }
  }
  //addList放啊对Input输入数据进行处理
  addToList = (text)=>{
    this.setState({//利用setState改变state的值
      todos:[//对所有的todos数据操作
          ...this.state.todos,
          {
            text,//text:text,将Input内输入的值写入state的todos
            id: Math.random(),//id取随机数
            iscompleted: true//将完成状态默认设置为true
          }
      ]
    })
  }
  //根据id对List数据进行删除的方法
  ondeleteItem=(id)=>{
    //过滤掉传入的id值所指定的数据
    /*const todos = this.state.todos.filter(todo=>{//找到所有的与传入id不同的数据
      return todo.id !=id;
    });*/
    //简写过滤
    const todos = this.state.todos.filter(todo=>todo.id!==id);
    this.setState({
      todos//todos:todos将剩下的数据再赋值给state的todos
    })
  }
  //根据id对list数据进行完成状态修改
  onchangeStatus=(id)=>{
    const todos = this.state.todos.map(todo=>{
      //找到需要修改状态的数据
      if(todo.id===id){
        //将该条数据中的iscomplited的布尔值取反
        todo.iscompleted = ! todo.iscompleted;
      }
      return todo;
    });
    this.setState({
      todos
    });
  }
  //渲染显示TodoHeader、TodoInput、TodoList等组件
  render() {
    //定义变量保存总任务数量(state中todos的长度)
    const totalCount = this.state.todos.length;
    //定义变量保存未完成的任务数(过滤iscomplited!==true后返回的数组的长度)
    const uncompletedCount = this.state.todos.filter(todo=>todo.iscompleted!==true).length;
    //定义变量保存已完成的任务数(过滤iscomplited===true后返回的数组的长度)
    const completedCount = this.state.todos.filter(todo=>todo.iscompleted===true).length;
    return (
      <div>
        <TodoHeader />
        <section className="hero is-light">
          <div className="hero-body">
            <div className="container">
              {/* 将addToList数据处理函数传出去 */}
              <TodoInput addToList={this.addToList}/>
              {/* 通过state内的数据控制List的显示 */}
              <TodoList todos={this.state.todos} ondeleteItem={this.ondeleteItem} onchangeStatus={this.onchangeStatus}/>
              {/* 渲染显示Footer中的数值 */}
              <TodoFooter totalCount={totalCount} uncompletedCount={uncompletedCount} completedCount={completedCount} />
           </div>
          </div>
        </section>
      </div>
    )
  }
}

src目录的component目录下:
TodoHeader.js

/*
//第一种TodoHeader组件定义方法
import React from 'react'
//TodoHeader组件
const TodoHeader = ()=>{
    return (
        <div>
            <h2>点击按钮添加输入框内容到列表</h2>
        </div>
    )
}
//导出TodoHeader
export default TodoHeader;*/
//第二种TodoHeader组件定义方法
import React, { Component } from 'react'
export default class TodoHeader extends Component {
  render() {
    return (
      <section className="hero is-primary">
      <div className="hero-body">
        <div className="container">
          <h1 className="title">
            待办事项列表
          </h1>
          <h2 className="subtitle">
            将输入框中的输入显示到表格并进行操作
          </h2>
        </div>
      </div>
    </section>
    )
  }
}


TodoInput.js

import React, { Component , createRef} from 'react'
//TodoInput组件
export default class TodoInput extends Component {
  //添加数据管理state保存input内的值
  constructor(){
    super();
    this.state={inputValue:''};
    this.inputRef= createRef();
  } 
  //每次输入完成调用
  changeHandler=(e)=>{
    this.setState({//将当前input输入的数据存入state的inputValue
        inputValue: e.currentTarget.value//e.currentTarget当前改变事件指向的input输入框
    })
  }
  //每次输入之后点击添加按钮调用
  clickHandler=()=>{
    //当input输入框为空时,无法实现添加
    if(this.state.inputValue ===''){
      return;
    }
    //调用addToList数据处理函数对当前inputValue里的值进行处理
    this.props.addToList(this.state.inputValue);
    //通过setState设置state的inputValue的值
    this.setState({
      //将inputValue的值设置为空字符串
      inputValue:''
    });
    //点击添加后保持input输入框聚焦
    this.inputRef.current.focus();
  }
  //触发enter键处理提交数据
  keyupHandler=(e)=>{
    //当触发enter键
    if(e.keyCode===13){
      //调用添加按钮的处理函数实现与点击按钮相同后的相同效果
      this.clickHandler()
    }
  }
  render() {
    return (
      // <div>
      //   {/*onChange={this.changeHandler}当输入框数据有变动时调用函数changeHandler将输入的数据放入inputValue  */}
      //   {/*value={this.state.inputValue}在将输入的值放入inputValue之后,再次从state里面取出inputValue的值再放入输入框  */}
      //   <input type="text" ref={this.inputRef} onChange={this.changeHandler} value={this.state.inputValue} onKeyUp={this.keyupHandler}/>
      //   {/*onClick = {this.clickHandler}当处理完输入框的数据后,调用clickHandler函数对input的值进行加入List的处理  */}
      //   <button onClick = {this.clickHandler}>添加</button>
      // </div>
       <div className="field has-addons">
       <div className="control">
       <input type="text" className="input" ref={this.inputRef} onChange={this.changeHandler} value={this.state.inputValue} onKeyUp={this.keyupHandler}/>
       </div>
       <div className="control">
         <button className="button is-primary" onClick = {this.clickHandler}>添加</button>
       </div>
     </div>
    )
  }
}


TodoList.js

import React, { Component } from 'react';
import classNames from 'classnames';//引入classnames
import PropTypes from 'prop-types';//引入prop-types
//TodoList组件
export default class TodoList extends Component {
/*//方法三:在constructor中改变this指向
  constructor(){
      super();
      //改变this指向,避免性能问题
      this.deleteHandler = this.deleteHandler.bind(this);
  }*/ 
  //props数据检测
  static propTypes = {
    todos: PropTypes.arrayOf(//数组检测
      PropTypes.shape({//对象检测
        id: PropTypes.number.isRequired,//数字检测
        text: PropTypes.string.isRequired,//字符串检测
        iscompleted: PropTypes.bool.isRequired//布尔值检测
      }).isRequired
    ).isRequired
  }
  deleteHandler(id){
    //调用ondeleteItem传递需要删除的id
    this.props.ondeleteItem(id);
  }
  changeStatus(id){
    //调用onchangeStatus传递需要更改状态的id
    this.props.onchangeStatus(id);
  }
  render() {
    return (
        <table className="table is-striped">
        <thead>
          <tr>
          <th>id</th>
          <th>任务</th>
          <th>完成状态</th>
          <th>操作</th>
          </tr>
        </thead>
        <tbody>
          {//利用map实现循环,对todos里的数据进行遍历,每一条数据均为一个todo
            this.props.todos.map(todo => {//每条数据返回显示一条带有自身(todo)的数据的行
              //将判断条件todo.iscompleted===true用一个变量iscompleted保存下来
              const iscompleted = todo.iscompleted===true;
              return  (
                <tr key={todo.id}>
                  <td>{todo.id}</td>
                  <td>{todo.text}</td>
                  <td>
                  {/* //当isCompleted为true时显示已完成,否则显示未完成 */}
                  {/* {todo.iscompleted === true ?<span>已完成</span>:<span>未完成</span>} */}
                  {/* 优化 利用classnames的classNames方法对span元素的className进行判断处理,当满足iscompleted(即todo.iscompleted===true)时,
                  添加className为is-primary且显示已完成,不满足时,添加className为is-warning且显示未完成*/}
                  <span className={classNames("tag",{
                    "is-primary" : iscompleted,
                    "is-warning" : !iscompleted
                  })}>{iscompleted? "已完成":"未完成"}</span>
                  </td>
                  <td>
                    <div className="buttons has-addons">
                        {/*方法一:this.deleteHandler.bind(this,todo.id)利用bind不执行函数情况下改变this指向,并将id传递过来  */}
                      <button className="button is-warning" onClick={this.deleteHandler.bind(this, todo.id)}>删除</button>
                        {/*方法二:利用箭头函数解决this指向问题,直接调用deleteHandler函数并将id作为参数传递  */}
                        {/* <button ondelete={()=>{this.deleteHandler(todo.id)}}>删除</button> */}
                      <button className="button is-primary" onClick={this.changeStatus.bind(this, todo.id)}>切换状态</button>
                    </div>
                  </td>
                </tr>
              )
            })
          }
        </tbody>
        </table> 
    )
  }
}

TodoFooter.js

import React, { Component } from 'react';
import PropTypes from 'prop-types';//引入prop-types
export default class TodoFooter extends Component {
  //定义静态的propTypes对props中的数据进行检测totalCount: PropTypes.number.isRequired(totalCount是必须有输入/存在的数据类型)
  static propTypes = {
    totalCount: PropTypes.number.isRequired,
    uncompletedCount: PropTypes.number,
    completedCount: PropTypes.number
  }
  render() {
    //从props中解构出totalCount,completedCount,uncompletedCount
    const{totalCount,completedCount,uncompletedCount} = this.props
    return (
      <div>
        <div>总任务:{totalCount}</div>
        <div>已完成任务:{uncompletedCount}</div>
        <div>未完成任务:{completedCount}</div>
      </div>
    )
  }
}

上一篇 下一篇

猜你喜欢

热点阅读