博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
React, Redux的入门程序 - Todos
阅读量:6940 次
发布时间:2019-06-27

本文共 11867 字,大约阅读时间需要 39 分钟。

发现最近看到的框架入门程序都变成Todos,我花了三个小时才自己实现了一个Todos...感叹前端入门越来越复杂了,怀念几年前还是hello world的时代。。。

吐槽不多说,这里主要说的只是ReactRedux这一块儿,css样式完全是从抄过来的。

代码的结构准备是这个样子的:

clipboard.png

转化成代码结构,就是这个样子:

clipboard.png

另外,按照官方示例,在Header左边的toggleAll按钮放到了Section中。

Redux

Types/todos.js

Redux中,type是用于actionreducer交流的时候的一个flag,让reducer知道这是一个什么请求。

我比较习惯把type单独分离出来,列到一个文件里面,让Redux的文件更干净,并便于管理。

/** ------------------------- TODO ---------------------*/export const TODO_INSERT_ITEM = 'TODO_INSERT_ITEM';export const TODO_DELETE_ITEM = 'TODO_DELETE_ITEM';export const TODO_SWITCH_FILTER = 'TODO_SWITCH_FILTER';export const TODO_TOGGLE_ACTIVE = 'TODO_TOGGLE_ACTIVE';export const TODO_TOGGLE_ALL = 'TODO_TOGGLE_ALL';export const TODO_CHANGE_VALUE = 'TODO_CHANGE_VALUE';export const TODO_CLEAR_COMPLETED = 'TODO_CLEAR_COMPLETED';

Actions/todos.js

根据上面列出的type,列出对应的action creator

import { TODO_INSERT_ITEM, TODO_DELETE_ITEM, TODO_SWITCH_FILTER, TODO_TOGGLE_ACTIVE, TODO_TOGGLE_ALL, TODO_CHANGE_VALUE, TODO_CLEAR_COMPLETED } from '../types';// 插入一个TODO export function insertItem(value){  return {    type: TODO_INSERT_ITEM,    value  };}// 删除一个TODOexport function deleteItem(id) {  return {    type: TODO_DELETE_ITEM,    id  }}// 转换一个TODO的状态export function switchFilter(filter) {  return {    type: TODO_SWITCH_FILTER,    filter  }}// 清楚所有完成的TODOexport function clearCompleted(){  return {    type: TODO_CLEAR_COMPLETED  }}export function toggleActive(id){  return {    type: TODO_TOGGLE_ACTIVE,    id  }}// 转换所有的状态到activeexport function toggleAll(active){  return {    type: TODO_TOGGLE_ALL,    active  }  }// 改变对应TODO的值export function changeValue(id, value) {  return {    type: TODO_CHANGE_VALUE,    id,    value  }}

Reducers/todos.js

reducer中需要注意几点:

  1. 初始化的state要从localStorage中获取

  2. 每次做出修改,都要重新更新localStorage

  3. 数据没有发生改变的时候,尽量使用原数据,减少re-render

  4. 为了便于查找,我在这里用了lodashuniqueId方法,给每一个item加一个id

  5. 为了便于储存和展示,我这里包含一个items用来保存所有的items,一个showedItems用来储存需要展示的items

先提供一个简单的简写localStorage方法

const local = (function(KEY){  return {    set: value=>{ localStorage.setItem(KEY, value) },    get: ()=>localStorage.getItem(KEY),    check: ()=>localStorage.getItem(KEY) != undefined  };})("todo");

然后几个辅助的方法:

// 制造一个新的itemfunction generateItem(value) {  return {    id: _.uniqueId(),    active: true,    value  }}// 判断当前的item是否正在展示function include(active, filter) {  return filter === "ALL" || (active && filter === "ACTIVE")  || (!active && filter === "COMPLETED");}// 获取页面上需要展示的itemsfunction getShowedItems(items, filter) {  let showedItems = [], keys = Object.keys(items);  for(let i = 0; i < keys.length; i++){    let item = items[keys[i]];    if(include(item.active, filter)) {      showedItems.push(item);    }  }  return showedItems;}

初始化的时候,获取localStorage中的值,或者给一个默认值:

let defaultTodo;(function(){  if(local.check()) {    defaultTodo = JSON.parse(local.get());  } else {    defaultTodo = {      items: {},      filter: "ALL", // ALL, COMPLETED, ACTIVE      count: 0,      showedItems: [],      hasCompleted: false    }  }})();

注:在这里提一句,由于我不喜欢文档中把所有的处理方法放在一个函数里面的方式,所以我写了一个方法,把reducers分开成多个函数

// 很简单,其实就是循环调用。。。export function combine(reducers){  return (state, action) => {    for(let key in reducers) {      if(reducers.hasOwnProperty(key)) {          state = reducers[key](state, action) || state;      }    }    return state;  }}

下面上所有的reducers,具体逻辑就不多说了:

let exports = {};exports.insertItem = function(state = defaultTodo, action) {  const type = action.type;  if(type === TODO_INSERT_ITEM) {    let { count, items, filter, showedItems } = state;    let item = generateItem(action.value);    items = {      ...items,      [item.id] : item     }    count = count + 1;        state = {      ...state,      items,      count,      showedItems: filter !== "COMPLETED" ? getShowedItems(items, filter) : showedItems    }    local.set(JSON.stringify(state));  }  return state;}exports.deleteItem = function(state = defaultTodo, action) {  const type = action.type;  if(type === TODO_DELETE_ITEM && state.items[action.id]) {    let { count, items, filter, hasCompleted } = state;    let item = items[action.id];    delete items[action.id];    if(item.active) count--;        state = {      ...state,      items,      count,      showedItems: include(item.active, filter) ? getShowedItems(items, filter) : state.showedItems,      hasCompleted: Object.keys(items).length !== count    }    local.set(JSON.stringify(state));  }  return state;}exports.switchFilter = function(state = defaultTodo, action) {  const type = action.type;  if(type === TODO_SWITCH_FILTER && state.filter !== action.filter) {    state = {      ...state,      filter: action.filter,      showedItems: getShowedItems(state.items, action.filter)    }    local.set(JSON.stringify(state));  }  return state;}exports.clearCompleted = function(state = defaultTodo, action) {  const type = action.type;  if(type === TODO_CLEAR_COMPLETED) {    let { items, filter, showedItems } = state;    let keys = Object.keys(items);    let tempItems = {};    for(let i = 0; i < keys.length; i++) {      let item = items[keys[i]];      if(item.active) {        tempItems[item.id] = item;      }    }    state = {      ...state,      items: tempItems,      showedItems: filter === "ACTIVE" ? showedItems : getShowedItems(tempItems, filter),      hasCompleted: false    }    local.set(JSON.stringify(state));  }  return state;}exports.toggleActive = function(state = defaultTodo, action) {  const { type, id } = action;  if(type === TODO_TOGGLE_ACTIVE && state.items[id]) {    let { items, filter, count, showedItems } = state;        let item = items[id];    item.active = !item.active;        items = {      ...items,      [id]: item    };    if(item.active) count++; // 如果变为active    else count--; // 如果变为completed    state = {      ...state,      items,      count,      showedItems: getShowedItems(items, filter),      hasCompleted: Object.keys(items).length !== count    }    local.set(JSON.stringify(state));  }  return state;}exports.toggleAll = function(state = defaultTodo, action) {  const { type, active } = action;  if(type === TODO_TOGGLE_ALL) {    let { items, filter, showedItems } = state;    let keys = Object.keys(items);        for(let i = 0; i < keys.length; i++) {      items[keys[i]].active = active;    }    let count = active ? keys.length : 0;     state = {      ...state,      items,      count,      showedItems: include(active, filter) ? getShowedItems(items, filter) : showedItems,      hasCompleted: !active    }    local.set(JSON.stringify(state));  }  return state;}exports.changeValue = function(state = defaultTodo, action){  const { type, id } = action;  if(type === TODO_CHANGE_VALUE && state.items[id]) {    let { items, filter, showedItems } = state;    let item = items[id];    item.value = action.value;    items = {      ...items,      [id]: item        };    state = {      ...state,      items,      showedItems: include(item.active, filter) ? getShowedItems(items, filter) : showedItems    }    local.set(JSON.stringify(state));  }  return state;}export default combine(exports); // 用combine方法包裹

Reducers中,我在很多的showedItems都做了是否发生改变的检查,如果没有发生改变,那么就用原来的,便于在Section组件中,可以避免不必要的重新渲染。虽然,在我这里似乎没有什么用。不过对复杂的项目还是有必要的。

Views/Todos.js

import React from 'react';import Header from 'containers/ToDo/Header';import Footer from 'containers/ToDo/Footer';import Section from 'containers/ToDo/Section';import 'components/ToDo/index.scss';export default class ToDo extends React.Component {  constructor(props) {    super(props);  }  render(){    return (      
) }}

Contianers

Header.js

Header.js主要负责logo的渲染,和那个input框的功能。

利用controlled componentinput的值进行控制,然后监听键盘来判断是输入还是提交。

import React from 'react';import { CONTROLS } from 'utils/KEYCODE';import { connect } from 'react-redux';import { insertItem } from 'actions/todo';class Header extends React.Component {  constructor(props) {    super(props);    this.state = {      value: ""    };  }  onChange = (e)=>{    let value = e.target.value;    this.setState({      value    });  }  onKeyDown = (e)=>{    let keyCode = e.keyCode;    if(keyCode === CONTROLS.ENTER && this.state.value !== "") {      this.props.insertItem(this.state.value);      this.setState({        value: ""      });      e.preventDefault();      e.stopPropagation();    }  }  render(){    return (      

todos

) }}export default connect(null, { insertItem })(Header);

Footer.js

Footer主要是用于展示数量filter, Clear Completed按钮

import React, { PropTypes } from 'react';import { connect } from 'react-redux';import { switchFilter, clearCompleted } from 'actions/todo';class Footer extends React.Component {  constructor(props) {    super(props);  }  switchFilter = (filter)=>{    this.props.switchFilter(filter.toUpperCase());  }  render(){    const { count, hasCompleted, filter, clearCompleted } = this.props;    if(count === 0 && !hasCompleted) return null;    return (      
) }}Footer.propTypes = { count: PropTypes.number.isRequired, hasCompleted: PropTypes.bool.isRequired, filter: PropTypes.oneOf(['ALL', 'ACTIVE', 'COMPLETED']).isRequired}function mapStateToProps(state){ let { todo: { count, hasCompleted, filter } } = state; return { count, hasCompleted, filter }}export default connect(mapStateToProps, { switchFilter, clearCompleted })(Footer);

Section.js

Section包含Todos的列表,还有删除, 改变状态修改value, toggle all等功能。

import React, { PropTypes } from 'react';import { connect } from 'react-redux';import { deleteItem, toggleActive, toggleAll, changeValue } from 'actions/todo';import Item from 'components/ToDo/Item';class Section extends React.Component {  constructor(props) {    super(props);  }  render(){    const { showedItems=[], count, toggleAll, changeValue, deleteItem, toggleActive, hasCompleted } = this.props;    return (      
{ toggleAll(count === 0) }} checked={count === 0 && hasCompleted} />
    { showedItems.map(item=>
    ) }
) }}Section.propTypes = { showedItems: PropTypes.arrayOf(PropTypes.object).isRequired, count: PropTypes.number.isRequired}function mapStateToProps(state) { let { todo: { showedItems, count, hasCompleted } } = state; return { showedItems, count, hasCompleted };}export default connect(mapStateToProps, { deleteItem, toggleActive, toggleAll, changeValue })(Section);

Components

Item.js

import React from 'react';import ReactDOM from 'react-dom';export default class Item extends React.Component {  constructor(props) {    super(props);    this.state = { value: props.value, editing: false };  }  componentDidUpdate() {    if(this.state.editing) {      var node = ReactDOM.findDOMNode(this.edit);      node.focus();    }  }  inputInstance = (input) => {    this.edit = input;  }  onToggle = (e)=>{    this.props.toggleActive(this.props.id, !e.target.checked);  }  onValueChange = (e)=>{    this.props.onValueChange(this.props.id, e.target.value);    this.setState({      editing: false    });  }  onEditChange = (e)=>{    this.setState({      value: e.target.value    });  }  onDoubleClick = (e)=>{    this.setState({      editing: true    });  }  render(){    let { id, active, onItemDelete, onValueChange } = this.props;    let { value, editing } = this.state;    return (      
  • { editing || (
    ) } { editing && }
  • ) }}

    写组件的时候,感觉代码贴出来看看就好了。需要讲解的不多。。。

    转载地址:http://wfsnl.baihongyu.com/

    你可能感兴趣的文章
    nginx错误:unknown directive "锘? in F:\nginx/conf/nginx.conf:3
    查看>>
    【数据结构-文都】第二章 线性表(1)
    查看>>
    Ubuntu快捷键
    查看>>
    mycp
    查看>>
    python连续爬取多个网页的图片分别保存到不同的文件夹
    查看>>
    linux之SQL语句简明教程---UNION ALL
    查看>>
    iphone-common-codes-ccteam源代码 CCAudio.mm
    查看>>
    C++随笔:.NET CoreCLR之GC探索(3)
    查看>>
    rar 按日期时间备份
    查看>>
    了解url
    查看>>
    时间记录日志
    查看>>
    自我介绍
    查看>>
    Node.js
    查看>>
    航空公司判定图练习
    查看>>
    进程 线程通信方式(转载)
    查看>>
    1061 Dating
    查看>>
    在ios上,fixed定位因为input导致手机下面出现空白,视图变小
    查看>>
    SharePoint 定期备份网站
    查看>>
    1415-2团队博客汇总表
    查看>>
    Android Drawable Resource学习(十)、ScaleDrawable
    查看>>