掌握聚合最新动态了解行业最新趋势
API接口,开发服务,免费咨询服务

如何使用 React, Redux, and Immutable.js构建一个待办事项APP

React 使用组件和单向数据流的方式,使其成为描述用户界面结构的理想选择。然而组件与状态工作的接口却一直故意设计的很简单,以提醒我们 React 只是传统Model-View-Controller 模型中的视图。

没有什么可以阻止我们只使用 React 来构建大型应用,但是很快的,你就会发现,为了保持代码足够简洁,可能需要在其他地方管理状态。

虽然没有处理应用程序状态的官方解决方案,但是有些库与React的范例特别吻合。今天我们将React与两个这样的库配对,并使用它们构建一个简单的应用程序。

Redux

Redux 是一个极小的库,通过结合 Flux 和 Elm 的理论并加以完善,它可以充当我们应用程序状态的容器, 我们可以使用 Redux 管理任何种类应用的状态,下面提供我们需要遵守的两条准则:

  1. 单一数据源

  2. State 是只读的,惟一改变 state 的方法就是触发 action (原文:Changes come from actions not mutations)

Redux 存储的核心函数是使用当前应用程序状态,操作并将其组合起来以创建新的应用程序状态的功能。 我们称这个函数为reducer。

我们的React组件将负责将操作发送到store 中,反过来store 会告诉组件何时需要重新渲染。

ImmutableJS

由于Readux 不允许我们直接修改应用的 state,所以通过使用不可变数据结构对应用程序状态进行建模来实施这一点是有帮助的。

ImmutableJS 通过从Clojure和Scala中得到的启发为我们提供了许多不可变的数据结构,并且提供了以高效的方式实现的改变数据的接口。

Demo

我们将使用React结合redux和 ImmutableJS 构建一个简单的待办事项清单应用。它允许我们添加待办事项,并可以在完成和未完成间进行切换。

在 CodePen中查看 SitePoint (@SitePoint)的React, Redux & Immutable Todo 源码。

这份代码也可以通过github)查看。

Setup

我们将从创建项目目录和使用npm init初始化一个package.json 文件开始。然后安装我们所必需的依赖。

npm install --save react react-dom redux react-redux immutable
npm install --save-dev webpack babel-loader babel-preset-es2015 babel-preset-react

我们将使用 JSX 和ES2015,所以我们将使用 Babel来编译我们的代码。我们将这一操作作为Webpack模块打包过程的一部分来执行。

首先我们在webpack.config.js文件中创建Webpack 配置。

module.exports = {
  entry: './src/app.js',
  output: {
    path: __dirname,
    filename: 'bundle.js'
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel',
        query: { presets: [ 'es2015', 'react' ] }
      }
    ]
  }
};

最后我们需要在package.json 中添加一个npm script对源码进行编译。

"scripts": {
  "build": "webpack --debug"
}

我们需要在我们想编译代码的时候执行 npm run build 命令。

React 和组建

在我们编写任何组件之前,创建一些虚拟数据可能会有所帮助。这样可以让我们清楚我们的组件需要渲染什么。

const dummyTodos = [
  { id: 0, isDone: true,  text: 'make components' },
  { id: 1, isDone: false, text: 'design actions' },
  { id: 2, isDone: false, text: 'implement reducer' },
  { id: 3, isDone: false, text: 'connect components' }
];

对于这个应用,我们只需要两个React 组件Todo和TodoList。

// src/components.js

import React from 'react';

export function Todo(props) {
  const { todo } = props;
  if(todo.isDone) {
    return <strike>{todo.text}</strike>;
  } else {
    return <span>{todo.text}</span>;
  }
}

export function TodoList(props) {
  const { todos } = props;
  return (
    <div className='todo'>
      <input type='text' placeholder='Add todo' />
      <ul className='todo__list'>
        {todos.map(t => (
          <li key={t.id} className='todo__item'>
            <Todo todo={t} />
          </li>
        ))}
      </ul>
    </div>
  );
}

此刻,我们可以在项目目录中创建一个index.html用下面的代码来测试这些组件。(你可以在GitHub找到一个简单的样式表。)

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="style.css">
    <title>Immutable Todo</title>
  </head>
  <body>
    <div id="app"></div>
    <script src="bundle.js"></script>
  </body>
</html>

我们还需要在src/app.js有一个入口点(entry point);

// src/app.js

import React from 'react';
import { render } from 'react-dom';
import { TodoList } from './components';

const dummyTodos = [
  { id: 0, isDone: true,  text: 'make components' },
  { id: 1, isDone: false, text: 'design actions' },
  { id: 2, isDone: false, text: 'implement reducer' },
  { id: 3, isDone: false, text: 'connect components' }
];

render(
  <TodoList todos={dummyTodos} />,
  document.getElementById('app')
);

使用 npm run build命令编译代码之后,在你的浏览器打开 index.html文件,并确保它可以工作。

Redux & ImmutableJS

现在我们感觉用户界面还不错,我们可以开始考虑它背后的状态。假数据是一个很好的切入点,我们可以很容易把它转换成ImmutableJS Map。

import { List, Map } from 'immutable';

const dummyTodos = List([
  Map({ id: 0, isDone: true,  text: 'make components' }),
  Map({ id: 1, isDone: false, text: 'design actions' }),
  Map({ id: 2, isDone: false, text: 'implement reducer' }),
  Map({ id: 3, isDone: false, text: 'connect components' })
]);

ImmutableJS Map 与 JavaScript对象的工作方式不同,所以我们需要对组件进行一些微调。在每个属性访问的(例如todo.id)地方都需要改为方法调用(todo.get('id'))。

设计 Actions

现在已经搞定了shape和机构,我们可以开始考虑Action 如何更新它。在这个实例中我们需要两个 Action,一个用于添加待办事项,另一个用于切换现有事项的状态(完成/未完成)。

定义一些方法来创建这些actions;

// src/actions.js

// succinct hack for generating passable unique ids
const uid = () => Math.random().toString(34).slice(2);

export function addTodo(text) {
  return {
    type: 'ADD_TODO',
    payload: {
      id: uid(),
      isDone: false,
      text: text
    }
  };
}

export function toggleTodo(id) {
  return {
    type: 'TOGGLE_TODO',
    payload: id
  }
}

Each action is just a JavaScript object with a type and payload properties. The type property helps us decide what to do with the payload when we process the action later. 每个action只是一个具有 type 和 payload 属性的 JavaScript 对象。当我们稍后处理操作时,type属性可以帮助我们决定如何处理相应payload的todo。

设计一个 Reducer

现在我们知道状态的样子和更新它的动作,我们可以构建我们的reducer。提示一下,reducer只是一个接受 一个state 和一个action,并使用它们计算新state的方法。

这是我们的reducer的初始化结构。

// src/reducer.js

import { List, Map } from 'immutable';

const init = List([]);

export default function(todos=init, action) {
  switch(action.type) {
    case 'ADD_TODO':
      // ...
    case 'TOGGLE_TODO':
      // ...
    default:
      return todos;
  }
}

处理ADD_TODO action是很简单的,因为我们可以使用 .push() 方法,这个方法将返回一个新的list将待办事项添加在todos的末尾。

case 'ADD_TODO':
  return todos.push(Map(action.payload));

注意,在我们push到list之前,我们将todo object转换为了一个immutable map。

我们需要处理的更复杂的action是TOGGLE_TODO。

case 'TOGGLE_TODO':
  return todos.map(t => {
    if(t.get('id') === action.payload) {
      return t.update('isDone', isDone => !isDone);
    } else {
      return t;
    }
  });

我们使用.map() 迭代list,找到与action的id匹配的todo。然后调用.update() 方法,它接受一个key和一个function,然后返回一个从map中复制的新的todo。这个todo的值会使用调用update方法并传递初始值返回的结果替换。

这是可能有助于查看的文字版本。

const todo = Map({ id: 0, text: 'foo', isDone: false });
todo.update('isDone', isDone => !isDone);
// => { id: 0, text: 'foo', isDone: t
                                        

声明:所有来源为“聚合数据”的内容信息,未经本网许可,不得转载!如对内容有异议或投诉,请与我们联系。邮箱:marketing@think-land.com

0512-88869195
数 据 驱 动 未 来
Data Drives The Future