基于dva-cli实现todoList

功能点

  • 输入框输入内容回车, 增加一条记录, 同时输入框内容被清空
  • 双击每一条记录的文字内容, 进行编辑修改, 回车或者点击其他区域完成修改
  • 点击每一条记录前面的圆圈, 切换该记录的完成或已完成状态, 圆圈和后面的文字内容随着状态的切换均有样式变化
  • 每一条记录均有删除图标, 点击完成该条记录的删除操作
  • 动态显示未完成事项的个数
  • 三个按钮All, Active, Completed可完成对当前所有事项, 未完成事项, 已完成事项的查询显示
  • 可一次性清除所有已完成事项
  • 所有数据的永久性存储, 即刷新页面或再次启动项目数据仍然保持上次操作的数据记录

使用antd的Input+Form控件实现输入框UI, 利用getFieldDecorator()将Input和表单进行双向绑定,每次用户向输入框输入内容并回车后,触发回调事件,通过getFieldsValue()拿到输入框的内容,并将该内容连同iscompletedid属性作为一个对象推入数组list中,发送dispatch请求完成list的更新。

通过自定义不同的页面路由,但使用同一个页面模板,完成全部事项、未完成事项和已完成事项的筛选显示。

在组件中import pathToRegexp from 'path-to-regexp',并传入location对象,通过location.pathname判断当前页面的路由。

对数组list使用filter()方法,根据当前的路由,过滤出所有iscompletedtruefalse的项。

每一条事项的展示放在component展示组件中,即对数组进行map()遍历操作,返回一个子组件,对这个子组件传入这个数组数组的每一项,以及每一项的索引,作为每一条事项的展示组件完成每一条事项的展示。

let todos = []
  if (list.length) {
    let showList = list.filter((value) => {
      //根据当前路由, 确定list数组中需要展示的项
      switch (pathname){
        case '/active': return value.iscompleted === false
        case '/completed': return value.iscompleted === true
        default: return true
      }
    })

    todos = showList.map(function(item, index) {
      //子组件用来定义每一项的展示, 传入展示项以及索引, 和每一个react组件所必须的唯一key值
      return 
    })
  }

在点击事件中携带该项纪录的索引值data_key发送dispatch请求,(react组件的唯一key值无法传入组件内部),利用数组的splice()方法(改变原数组)完成数组项的删除操作.

    deleteItem(state, action) {
      state.list.splice(action.payload.data_key, 1)

      localStorage.setItem('list',JSON.stringify(state.list))

      return {
        ...state
      }
    }

容器组件的声明采用stateless写法, 展示组件的声明采用ES6写法class TodoItem extends Component{}.

编辑的时候涉及两个状态的变化: isEditing是否编辑以及editText编辑框input的内容。双击文字内容,改变isEditing为true,使隐藏的input框便显示出来。

const ESCAPE_KEY = 27;
const ENTER_KEY = 13;

class TodoItem extends Component {
  constructor(props) {
    super(props);
    this.state = {
      isEditing: false,
      editText: this.props.todo.item
    }
  }

  changeEditState() {
    this.setState({
      isEditing: true
    })
  }

  handleChange(e) {
    console.log(e.target.value)
    this.setState({
      editText: e.target.value
    });

  }

  handleSubmit(e) {
    let val = this.state.editText.trim();
    if (val) {
      // dispatch save
      console.log(val)
      this.setState({
        isEditing: false
      })
    }
  }


  handleKeyDown(event) {
    if (event.which === ESCAPE_KEY) {
      this.setState({
        editText: this.props.todo.item,
        isEditing: false
      });
    } else if (event.which === ENTER_KEY) {
      this.handleSubmit(event);
    }

    this.props.dispatch({
      type: `toDos/applyEdit`,
      payload: {
        editText: this.state.editText,
        id: this.props.todo.id
      }
    })
  }

  componentDidUpdate(prevProps, prevState) {
    //处理光标
    if (!prevState.isEditing && this.state.isEditing) {
      let node = ReactDom.findDOMNode(this.refs.editField);
      node.focus();
      console.log(node.value)
      node.setSelectionRange(node.value.length, node.value.length);
    }
  }

  render() {
    const {
      todo,
      dispatch,
      toDos
    } = this.props;
  }

    return (
        
{todo.item}
) }

数据的存储

localStorage是html5提供的一种本地存储的方法,可以把数据存储在本地浏览器,下次打开后仍然可以获取到存储的数据,如果在存储的数据量小的时候可以起到代替数据库的功能,比cookies更有优越性。特点:永久性存储.

Web Storage API --点这里
localStorage.setItem()方法用来创建新数据项和更新已存在的值。该方法接受两个参数——要创建/修改的数据项的键,和对应的值。
localStorage.getItem()可以从存储中获取一个数据项。该方法接受数据项的键作为参数,并返回数据值。

目前所有的浏览器中都会把localStorage的值类型限定为string类型,这个在对日常比较常见的JSON对象类型需要一些转换.

在对数据进行增删改操作之后,即数据list有所更改变化后,调用Storage.setItem()方法实时更改替换上次存储的数据。
localStorage.setItem('list',JSON.stringify(data))

在model文件的subscriptions中,每当进入页面路由,就从存储中取出所有数据。

      history.listen(({ pathname, query }) => {
        if (pathToRegexp(`/`).test(pathname) || pathToRegexp(`/completed`).test(pathname) || pathToRegexp(`/active`).test(pathname)) {
          let data = localStorage.getItem('list') || ''//取出数据
          dispatch({
            type: 'updateState',
            payload: {
              list: JSON.parse(data)//将数据装换成JSON对象,更新全局list
            }
          })
        }        
      }) 

总结

  1. 组件结构的设计
    容器组件和展示组件: 前者关注逻辑的处理和状态的变化, 后者设计成可复用的公共组件, 不涉及状态的处理, 只负责展示.
  //子组件
     let  todos = showList.map(function(item, index) {
        //每一条事项
        return 
    })

    let footer 
    if (list.length) {
      //操作按钮行
      footer = 
}
  1. 数据形式的设计
    将每一条事项作为一个对象,放入一个数组中。数组的每一项包括每一条事项的内容item, 是否完成iscompleted, 以及id标识。通过查找id完成每一项数据的更改操作。
      {
          item: item,
          iscompleted: false,
         // isEditing: false,
          id: id
        }
  1. react中样式的处理
  • CSS Modules的使用: 点击
import styles from './TodoItem.css'
//使用:
    

CSS Modules提供了compose组合方法来处理样式复用

.correct{

}
.correct::before{
    position: relative;
    top: -10px;
}

.btnbasic {
    composes:correct; //compose组合
    float: left;
    border-radius: 50%;
    border: 1px solid #e4dada;
    width: 30px;
    height: 30px;
    margin: 10px 0;
    text-align: center;
    color: green;
    cursor: pointer;
}
  • classnames
    首先安装npm install classnames

If you are using css-modules, or a similar approach to abstract class "names" and the real className values that are actually output to the DOM, you may want to use the bind variant.

在CSS Modules中使用classnames需要绑定变量bind

   let classNames = require('classnames/bind')
   //用法:
    const itemClass = cx('item')

    const btnClass = cx({
      btnbasic: true,
      'iconfont icon-correct': iscompleted
    })

    

classnames介绍

你可能感兴趣的:(基于dva-cli实现todoList)