资讯专栏INFORMATION COLUMN

React, Redux的入门程序 - Todos

qqlcbb / 2526人阅读

摘要:发现最近看到的框架入门程序都变成,我花了三个小时才自己实现了一个感叹前端入门越来越复杂了,怀念几年前还是的时代。。。主要负责的渲染,和那个框的功能。利用对的值进行控制,然后监听键盘来判断是输入还是提交。需要讲解的不多。。。

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

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

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

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

另外,按照官方示例,在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
  };
}

// 删除一个TODO
export function deleteItem(id) {
  return {
    type: TODO_DELETE_ITEM,
    id
  }
}

// 转换一个TODO的状态
export function switchFilter(filter) {
  return {
    type: TODO_SWITCH_FILTER,
    filter
  }
}

// 清楚所有完成的TODO
export function clearCompleted(){
  return {
    type: TODO_CLEAR_COMPLETED
  }
}

export function toggleActive(id){
  return {
    type: TODO_TOGGLE_ACTIVE,
    id
  }
}

// 转换所有的状态到active
export 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中需要注意几点:

初始化的state要从localStorage中获取

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

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

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

为了便于储存和展示,我这里包含一个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");

然后几个辅助的方法:

// 制造一个新的item
function generateItem(value) {
  return {
    id: _.uniqueId(),
    active: true,
    value
  }
}

// 判断当前的item是否正在展示
function include(active, filter) {
  return filter === "ALL" || (active && filter === "ACTIVE")  || (!active && filter === "COMPLETED");
}

// 获取页面上需要展示的items
function 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 && }
  • ) } }

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

    文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

    转载请注明本文地址:https://www.ucloud.cn/yun/81325.html

    相关文章

    • redux —— 入门实例 TodoList

      摘要:入门实例前端技术真是日新月异,搞完不搭配个数据流都不好意思了。关于的用法,这只是基础入门的部分,还有的多的搞基操作,比如异步数据流和配合。 redux —— 入门实例 TodoListshowImg(https://segmentfault.com/img/bVtSeH); Tip 前端技术真是日新月异,搞完 React 不搭配个数据流都不好意思了。满怀期待的心去翻了翻 flux,简直...

      SKYZACK 评论0 收藏0
    • Redux 入门

      摘要:系列文章入门本文进阶番外篇状态管理,第一次听到这个词要追溯到去年年底。只读的唯一改变的方法就是触发,是一个用于描述已发生事件的普通对象。没有特殊情况没有副作用,没有请求没有变量修改,只进行单纯执行计算。 系列文章: Redux 入门(本文) Redux 进阶 番外篇: Vuex — The core of Vue application 状态管理,第一次听到这个词要追溯到去年年...

      shusen 评论0 收藏0
    • MobX入门TodoList

      摘要:用于简单可扩展的状态管理,相比有更高的灵活性,文档参考中文文档,本文作为入门,介绍一个简单的项目。任务已完成下一个任务修复谷歌浏览器页面显示问题提交意见反馈代码创建在中引入主入口文件设置参考入门学习总结 MobX用于简单、可扩展的React状态管理,相比Redux有更高的灵活性,文档参考:MobX中文文档,本文作为入门,介绍一个简单的TodoList项目。 1. 预期效果 showIm...

      csRyan 评论0 收藏0
    • Taro集成Redux快速上手

      摘要:开发前需要安装和以及一些需要用到的中间件如果在要使用的话,还需要引入这个库或者使用示例下面通过实现一个快速上手。然后开始创建处理这两个指令的。完成上述三步之后,我们就可以在应用的主页使用相应修改并取得新的数据了。 本文适合有一定React和Redux基础的用户阅读。 前言的前言 最近被一款来自京东凹凸实验室的多终端开发框架Taro吸粉了,官方对 Taro 的简介是使用React语法,一...

      DevYK 评论0 收藏0
    • 实例讲解react+react-router+redux

      摘要:而函数式编程就不一样了,这是模仿我们人类的思维方式发明出来的。数据流在中,数据的流动是单向的,即从父节点传递到子节点。数据流严格的单向数据流是架构的设计核心。 前言 总括: 本文采用react+redux+react-router+less+es6+webpack,以实现一个简易备忘录(todolist)为例尽可能全面的讲述使用react全家桶实现一个完整应用的过程。 代码地址:Re...

      RiverLi 评论0 收藏0

    发表评论

    0条评论

    qqlcbb

    |高级讲师

    TA的文章

    阅读更多
    最新活动
    阅读需要支付1元查看
    <