Webpack&React (六) React 和 Flux

渣翻译,请看原文链接

React 和 Flux


尽管你可以一直都在组件中写另种逻辑,但最终这样做将是痛苦的.Flux 应用架构能让我们的React应用清爽起来.它不是唯一的解决方案,但却是一个不错的起点.

Flux让我们分离我们应用的数据和程序状态.这帮助我们保持它们的清晰和应用的可维护性.Flux是为了大型团队设计的.因此你可能会觉得它很详细.但我们可以直接使它到工作中提升我们的效率.

Flux简介


这里写图片描述
到目前为至,我们一直工作在视图中.Flux架构引入了几个新概念.它们是actions(行为),dispatcher(调度器)和stores(存储).与其它流行框架,如Angular或Ember相比,Flux实现的是数据单向流动的,尽管双向数据绑定带来了方便,但它是有代价的.我们很难理解发生了什么和为什么是这样的.

Actions(行为)和Stores(存储)

对我们而言,我们将建模NoteActionsNoteStore. NoteActions提供操作我们数据的具体方法.例如,我们有NoteActions.create({task: 'Learn React'}).

Dispatcher(调度器)

当我们触发了一个操作.调度器会得到通知.它能处理存储间依赖关系.调度器可能让我们在某些行为需要执行前执行其它行为.

最简单的方式,actions只是原样把消息传递给dispatcher.它也可以触发异步查询并且跟据最终结果进行调度.这使我们能处理接收的数据和可能的错误.

一旦调度程序处理一个行为,存储将监听它触发,对我们而言,NoteStore获得通知.最终它会更新内部状态.这之后,它会把这个新状态通知到合适的监听器.

Flux数据流

通常,这个单向的过程会形成一个循环.接下来的示例图是一个常见的流.它与之前是相同的,只是增加了一个反回.最终,通过这个循环过程,将会刷新我们依赖在store的相关的组件.

这听起来像是有许多步骤,例如实现一个简单的新建Note.但这种方法有它的好处,考虑到数据总是单向流动的,跟踪测试会很容易.如果发现问题,则必定出现在周期的某一环节上.
这里写图片描述

Flux的优势

虽然这听起来有点复杂,但是同样是灵活的.例如,我们能实现API通信,缓存,和视图的国际化.这种方式将保持逻辑的整洁,使应用程序更容易理解.

实现Flux架构会增加你的应用程序的代码量.要明白写最少的代码不是Flux的目标.它是被设计在团队中提高我们的生产力的工具.

使用哪种Flux实现?

选择哪个架构,归跟结底是你的个人喜好,你将考虑到如API,功能,文档,和技术支持,用比较流行的架构是一个不错的主意,等到你开始理解这个架构,可以让你更好的选择它. voronianski/flux-comparison提供了多种流行框架的比较.

移植到Alt


这里写图片描述

在这章,我们将使用一个知名的库Alt.它是一个灵活的,全功能的实现.

在Alt,你需要处理actions和stores,而隐藏了调度器,但你仍可以在需要时访问它.对比其它的实现,Alt隐藏了大量的样板.有指定的功能,允许你保存和还原应用程序状态.这对实现持续和通用的渲染来说是方便的.

配置Alt实例

我们从一个Alt实例开始.它保持跟踪actions和stores及其通信.让事情简单一些.我们处理所有Alt组件作为一个单例模式.

app/libs/alt.js

import Alt from 'alt';
//import chromeDebug from 'alt-utils/lib/chromeDebug';

const alt = new Alt();
//chromeDebug(alt);

export default alt;

Webpack会缓存这个模块.所以下次你引入Alt时.它将再反回你同样的实例.

如果你没有使用npm-install-webpack-plugin插件.记住要手动安装alt和其它工具到你的工程中npm i alt alt-container alt-utils node-uuid -S.

定义Notes的CRUD操作

下一步,我们需要定义一些基本的API来操作便签数据.考虑到读是隐含的,我们不需要这个.我们能模拟其它行为,Alt提供了一个generateActions.我们可以这样使用:

app/actions/NoteActions.js

import alt from '../libs/alt';

export default alt.generateActions('create', 'update', 'delete');

定义Notes的Store


store的唯一来源,是源于你应用程序状态的一部分.在本例中,我们需要一个维护便签状态,我们将通过bindActions函数来连接所有我们之前定义的行为(actions).

我们将把原先在App中store的逻辑移到NoteStore中.

设置骨架

第一步,我们能设置我们store的骨架,我们会在之后填充需要的方法.Alt使用标准的ES6类,即与我们之前所看到React组件相同的语法.下面是一个开始:

app/stores/NoteStore.js

import uuid from 'node-uuid';
import alt from '../libs/alt';
import NoteActions from '../actions/NoteActions';

class NoteStore {
  constructor() {
    this.bindActions(NoteActions);

    this.notes = [];
  }
  create(note) {

  }
  update(updatedNote) {

  }
  delete(id) {

  }
}

export default alt.createStore(NoteStore, 'NoteStore');

我们通过它的名字调用bindActions映射每一个行为(action).之后我们触发这个适当的逻辑在每个方法.最终.Alt使用alt.createStore连接存储.

注意指配一个标签给存储(在这个例子中为NoteStore)并不是必需的.但它是一个好的实践,特别是我们反复使用这个数据时,它会是重要的.

实现create

对比早期的逻辑,create将自动生成一个Note的id,这个细节能被隐藏在存储内部:

app/stores/NoteStore.js

import uuid from 'node-uuid';
import alt from '../libs/alt';
import NoteActions from '../actions/NoteActions';

class NoteStore {
  constructor() {
    ...
  }
  create(note) {
leanpub-start-insert
    const notes = this.notes;

    note.id = uuid.v4();

    this.setState({
      notes: notes.concat(note)
    });
leanpub-end-insert
  }
  ...
}

export default alt.createStore(NoteStore, 'NoteStore');

为了保持清爽.我们使用了this.setState.它是Alt的特性,让我们表示我们将要改变store的状态.Alt会把这个事件发给可能的监听器.

实现update

update与之前的写法大同小异.最重要的是我们通过this.setState提交新状态.

app/stores/NoteStore.js

...

class NoteStore {
  ...
  update(updatedNote) {
leanpub-start-insert
    const notes = this.notes.map(note => {
      if(note.id === updatedNote.id) {
        // Object.assign is used to patch the note data here. It
        // mutates target (first parameter). In order to avoid that,
        // I use {} as its target and apply data on it.
        //
        // Example: {}, {a: 5, b: 3}, {a: 17} -> {a: 17, b: 3}
        //
        // You can pass as many objects to the method as you want.
        return Object.assign({}, note, updatedNote);
      }

      return note;
    });

    // This is same as `this.setState({notes: notes})`
    this.setState({notes});
leanpub-end-insert
  }
  delete(id) {

  }
}

export default alt.createStore(NoteStore, 'NoteStore');

我们还剩下最后的操作,delete.

上面的{notes}是ES6的功能.被称为property shorthand.它等价于{notes: notes}.

实现delete

delete很简单.找到它之后删除.像之前一样.记着最后提交这个改变:

app/stores/NoteStore.js

...

class NoteStore {
  ...
  delete(id) {
leanpub-start-insert
    this.setState({
      notes: this.notes.filter(note => note.id !== id)
    });
leanpub-end-insert
  }
}

export default alt.createStore(NoteStore, 'NoteStore');

现在我们几乎整合了Flux在我们的应用程序中.我们设置了几个行为API到操作Notes数据.我们还有一个store用于实际的数据操作.我们还差了整合我们的视图.它会监听store并且能触发行为完成这个循环周期.

粘合在一起

这有一点点复杂,需要考虑的地方有很多.处理actions是容易的.例如,创建Note,我们需要触发NoteActions.create({task: 'New task'}).这将导致store改变,并且引发所有监听到它组件的改变.

我们的NoteStore将提供了两个方法重要的方法.是NoteStore.listenNoteStore.unlisten.它允许视图订阅状态的改变.

在上一章你可能还记得,React提供了生命周期钩子的设置.我们可以在视图中的componentDidMountcomponentWillUnmount中订阅NoteStore.记得注释监听,避免内存泄露的可能.

基于这个思想我们可以在App中组合NoteStoreNoteActions:

app/components/App.jsx

leanpub-start-delete
import uuid from 'node-uuid';
leanpub-end-delete
import React from 'react';
import Notes from './Notes.jsx';
leanpub-start-insert
import NoteActions from '../actions/NoteActions';
import NoteStore from '../stores/NoteStore';
leanpub-end-insert

export default class App extends React.Component {
  constructor(props) {
    super(props);

leanpub-start-delete
    this.state = {
      notes: [
        {
          id: uuid.v4(),
          task: 'Learn Webpack'
        },
        {
          id: uuid.v4(),
          task: 'Learn React'
        },
        {
          id: uuid.v4(),
          task: 'Do laundry'
        }
      ]
    };
leanpub-end-delete
leanpub-start-insert
    this.state = NoteStore.getState();
leanpub-end-insert
  }
leanpub-start-insert
  componentDidMount() {
    NoteStore.listen(this.storeChanged);
  }
  componentWillUnmount() {
    NoteStore.unlisten(this.storeChanged);
  }
  storeChanged = (state) => {
    // Without a property initializer `this` wouldn't
    // point at the right context because it defaults to
    // `undefined` in strict mode.
    this.setState(state);
  };
leanpub-end-insert
  render() {
    const notes = this.state.notes;

    return (
      <div>
        <button className="add-note" onClick={this.addNote}>+</button>
        <Notes notes={notes}
          onEdit={this.editNote}
          onDelete={this.deleteNote} />
      </div>
    );
  }
leanpub-start-delete
  deleteNote = (id) => {
    this.setState({
      notes: this.state.notes.filter(note => note.id !== id)
    });
  };
leanpub-end-delete
leanpub-start-insert
  deleteNote(id) {
    NoteActions.delete(id);
  }
leanpub-end-insert
leanpub-start-delete
  addNote = () => {
    this.setState({
      notes: this.state.notes.concat([{
        id: uuid.v4(),
        task: 'New task'
      }])
    });
  };
leanpub-end-delete
leanpub-start-insert
  addNote() {
    NoteActions.create({task: 'New task'});
  }
leanpub-end-insert
leanpub-start-delete
  editNote = (id, task) => {
    // Don't modify if trying set an empty value
    if(!task.trim()) {
      return;
    }

    const notes = this.state.notes.map(note => {
      if(note.id === id  && task) {
        note.task = task;
      }

      return note;
    });

    this.setState({notes});
  };
leanpub-end-delete
leanpub-start-insert
  editNote(id, task) {
    // Don't modify if trying set an empty value
    if(!task.trim()) {
      return;
    }

    NoteActions.update({id, task});
  }
leanpub-end-insert
}

这个应用程序应该与之前运行效果相同.当我们通过actions改变NoteStore,会通过setState级联我们的Appstate.这将会使组件render.这就是Flux的单向流动模式.

我们现在比之前的代码量增加了,但这没问题.App变的更简洁且以后开发会更容易.更重要的是我们已经成功的在我们的应用中实现了Flux架构.

localStorage上实现持久化


我们修改我们NoteStore的实现,对数据的改变进行保存。这样做我们刷新时不会丢失我们的数据。一种方式是使用localStorage.它是一个好的功能让我们持久化数据到浏览器。

理解localStorage

localStorage有一个兄弟是sessionStorage。但sessionStorage在浏览器关闭时会丢失数据。它们两个有下面相同的API:

  • storage.getItem(k) - 使用给定的键,反回一个存储的字符串值。
  • storage.removeItem(k) - 删除这个键对应的数据。
  • storage.setItem(k,v) - 存储这个键值对。
  • storage.clear() - 清空这个storage中的内容。

注意,使用浏览器的开发人员工具能很方便的操作它。例如,在Chrome中你能在Resources标签页中看这个状态。Console标签页中你能直接操作数据。你还可以使用storage.keystorage.key = 'value'快速修改。

localStoragesessionStorage存储数据不能超过10MB。虽然它很好用,但有些情况下会失效,如在IE中发生内存溢出时和在Safari的私有模式下。

实现localStorage的封装

为了更易于管理,我们可以实现对storage的简单的封装。

我们使用JSON.parseJSON.stringify进行序列化。在下面的实现里只需要storage.get(k)storage.set(k,v):

app/libs/storage.js

export default {
  get(k) {
    try {
      return JSON.parse(localStorage.getItem(k));
    }
    catch(e) {
      return null;
    }
  },
  set(k, v) {
    localStorage.setItem(k, JSON.stringify(v));
  }
};

使用FinalStore持久化应用

Alt专为这个意图,提供了一个内置的存储叫做FinalStore。我们可以使用FinalStore,引导和快照,持久我们全部的应用状态。FinalStore是一个监听所有已有存储的存储。每当有存储改变,FinalStore将了解它,这使得它非常适合持久化。

每当FinalStore改变,我们能取得整个应用的状态的快照并推它到localStoragealt.bootstra让我们设置所有存储的状态,这个方法不能发出事件,使我们的存储填充正确的状态,我们需要在组件渲染完成后调用它。在这个例子中,我们将取得数据从localStorage并引用它填充我们的存储。

为了整合这个思路到我们的应用,我们将需实现一个小的模块管理它。我们考虑可能的初始数据和触发新的逻辑。

app/libs/persist.js,将设置一个FinalStore,处理引导(恢复数据)和快照(保存数据)。

app/libs/persist.js

import makeFinalStore from 'alt-utils/lib/makeFinalStore';

export default function(alt, storage, storeName) {
  const finalStore = makeFinalStore(alt);

  try {
    alt.bootstrap(storage.get(storeName));
  }
  catch(e) {
    console.error('Failed to bootstrap data', e);
  }

  finalStore.listen(() => {
    if(!storage.get('debug')) {
      storage.set(storeName, alt.takeSnapshot());
    }
  });
}

最后,我们需要在初始化时调用这个持久化函数.我们需要传递相关数据到Alt实例,存储,存储名.

app/index.jsx

...
leanpub-start-insert
import alt from './libs/alt';
import storage from './libs/storage';
import persist from './libs/persist';

persist(alt, storage, 'app');
leanpub-end-insert

ReactDOM.render(<App />, document.getElementById('app'));

如果现在你刷新浏览器,程序将会记住这个状态.如果我们增加更多的存储到系统中,这个方案应该会是最省力的.即使整合一个真实的后端也不会有问题.

例如,你能传递初始值到你的HTML(全局渲染),加载它,然后持久化数据到后端,并且如要你想,你能使用localStorage作为一个备份.

Universal rendering(全局渲染?服务端渲染?)是有力的技术让你在使用React时改善应用的性能,并受SEO欢迎.不是所有组件都需要在前端进行渲染,我们会在后端执行一部分.我们在后端渲染初始化应用程序所需要的,并把它呈现给用户.同样还可以包含一些初始数据到你的应用中而不用执行额外的查询.

使用AltContainer

AltContainer封装会大大简化我们的连接逻辑.下面的实现展示怎么绑定所有在一起.注意我们删了多少代码:

app/components/App.jsx

leanpub-start-insert
import AltContainer from 'alt-container';
leanpub-end-insert
import React from 'react';
import Notes from './Notes.jsx';
import NoteActions from '../actions/NoteActions';
import NoteStore from '../stores/NoteStore';

export default class App extends React.Component {
leanpub-start-delete
  constructor(props) {
    super(props);

    this.state = NoteStore.getState();
  }
  componentDidMount() {
    NoteStore.listen(this.storeChanged);
  }
  componentWillUnmount() {
    NoteStore.unlisten(this.storeChanged);
  }
  storeChanged = (state) => {
    // Without a property initializer `this` wouldn't
    // point at the right context (defaults to `undefined` in strict mode).
    this.setState(state);
  };
leanpub-end-delete
  render() {
leanpub-start-delete
    const notes = this.state.notes;
leanpub-end-delete

    return (
      <div>
        <button className="add-note" onClick={this.addNote}>+</button>
leanpub-start-delete
        <Notes notes={notes}
          onEdit={this.editNote}
          onDelete={this.deleteNote} />
leanpub-end-delete
leanpub-start-insert
        <AltContainer
          stores={[NoteStore]}
          inject={{
            notes: () => NoteStore.getState().notes
          }}
        >
          <Notes onEdit={this.editNote} onDelete={this.deleteNote} />
        </AltContainer>
leanpub-end-insert
      </div>
    );
  }
  ...
}

AltContainer让我们绑定数据到直接的子节点.在本例中,它注入notes属性到Notes节点.这个模式让我们设置任意连接到多个存储并管理它们,你能在附录中找到其它的用法.

整合AltContainer绑到Alt组件中.如果你想的更远.你能把它封装到一个你自已的组件中.这个模式可以让你在以后用别的东西替换它.

调度器在Alt


尽管你可以一直不使用Flux的调度器,但理解它是有帮助的.Alt提供两种方式使用它.如果你想记录通过alt实例推进的所有事情,你可以使用一个代码段,如alt.dispatcher.register(console.log.bind(console)).或者,你可以触发this.dispatcher.register(...)在一个存储的构造函数中.这个机制让你实现有效的日志记录.

其它Flux的实现


尽管我们最终在我们的应用中使用了Alt,它不是响应一可选的.为了比较另种架构.我使用不同技术实现了这个相同的应用.下面是一些比较:

  • Redux 是一个Flux风格的架构.主要特点是热加载.Redux操作基于单一的状态树.状态树是使用reducers管理的.尽管有一些重复代码.Redux迫使你去钻研函数式编程.实现与Alt很接近.
  • 对比Redux,Cerebral起点不同.它支持.应用程序如何改变它的状态.
  • Mobservable 可以使你的数据结构可观测.

Relay?


对比Flux,Facebook的Relay改善数据获取部分.它让你推送必要的数据到视图层.它可以单独使用或在Flux中使用.

总结


在这章你学会了如何使用Flux架构开发一个简单的应用.在这个过程中学习了Flux的基本概念.现在我们将在应用中加入更多的功能.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要使用Webpack构建React应用,可以按照以下步骤进行操作: 1. 首先,在项目根目录下创建一个package.json文件,可以使用命令`npm init -y`快速创建。 2. 接下来,安装所需的依赖包。至少需要安装以下几个包:webpackwebpack-cli、babel-loader、@babel/core、@babel/preset-reactreact。 可以通过运行以下命令来安装这些包: ```shell npm install webpack webpack-cli babel-loader @babel/core @babel/preset-react react ``` 3. 在项目根目录下创建一个名为`webpack.config.js`的文件,用于配置Webpack。 在该文件中,至少包含以下内容: ```javascript const path = require('path'); module.exports = { entry: './src/index.js', // 入口文件 output: { path: path.resolve(__dirname, 'dist'), // 输出目录 filename: 'bundle.js' // 输出文件名 }, module: { rules: [ { test: /\.(js|jsx)$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-react'] } } } ] }, resolve: { extensions: ['.js', '.jsx'] } }; ``` 上述配置中,entry指定了入口文件,output指定了输出目录和文件名,module.rules中的配置指定了Babel加载器的使用,resolve.extensions配置允许在导入模块时省略后缀名。 4. 在根目录下创建一个名为`src`的文件夹,并在其中创建一个`index.js`文件,作为React应用的入口文件。 ```javascript import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; // 你的React组件文件 ReactDOM.render(<App />, document.getElementById('root')); ``` 5. 创建一个名为`App.js`的文件,并在其中编写React组件的代码。 ```javascript import React from 'react'; function App() { return <h1>Hello, React!</h1>; } export default App; ``` 6. 最后,在命令行中运行以下命令来构建React应用: ```shell npx webpack ``` 这将会在项目根目录下创建一个`dist`文件夹,并在其中生成一个`bundle.js`文件。这个文件就是Webpack构建后的输出文件。 现在,你已经成功使用Webpack构建了一个基本的React应用。你可以根据需要进一步配置和优化Webpack,以满足项目的需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值