2

本文转载自:众成翻译
译者:iOSDevLog
链接:http://www.zcfy.cc/article/3811
原文:https://www.fullstackreact.com/30-days-of-react/day-19/

随着我们了解了flux和Redux的知识,让我们将Redux整合到我们的应用中,并通过连接的应用。

昨天, 我们讨论了流量模式的原因, 它是什么, 我们有不同的选择, 以及介绍了Redux

今天, 我们将回到代码和添加Redux在我们的应用。现在我们正在用它构建的应用是简单的, 这只会显示我们最后一次获取当前时间的页面。为了简单起见, 我们不会调用远程服务器, 只需使用 JavaScript 的Date 对象。

第一件事, 我们使用redux将不得不安装redux库。我们可以使用npm 包管理器来安装redux。在我们以前构建的应用的根目录中, 让我们运行npm install 命令来安装redux:

npm install --save redux

想使用redux, 我们还需要安装另一个包, react-redux , 帮助我们把reactredux绑在一起。

npm install --save react-redux

配置和设置

接下来的工作, 我们需要做的是在我们的应用内建立redux。我们需要执行以下操作才能设置它:

  1. 定义归约

  2. 创建Store

  3. 创建动作创造者

  4. Store与我们的React意见联系起来

  5. 得益

第5步没有promises, 但它会更好, 嗯?

Precursor先驱

我们少量地讨论术语,(让我们的手指移动是更重要的)。我们将只是稍微调整我们的应用(烦人, 我知道, 但这是最后一次), 所以我们为了提供数据通过我们的应用可以创建一个包装组件。

完成后, 我们的应用树将具有以下形状:

[Root] -> [App] -> [Router/Routes] -> [Component]

废话少说, 让我们将我们的 src/App.js 移动到src/containers目录中, 我们需要同时更新来自我们的导入的一些路径。我们将使用几天前讨论的React路由材料。

我们将在 <Switch />语句中包含几条路由, 以确保一次只显示一个。

import React from 'react';

import {
  BrowserRouter as Router,
  Route,
  Switch
} from 'react-router-dom'

// We'll load our views from the `src/views`
// directory
import Home from './views/Home/Home';
import About from './views/About/About';

const App = props => {
  return (
    <Router>
      <Switch>
        <Route
          path="/about"
          component={About} />
        <Route
          path="*"
          component={Home} />
      </Switch>
    </Router>
  )
}

export default App;

此外, 我们将需要创建一个新的容器, 我们将调用Root , 这将包装我们的整个 <App />组件, 并使可用的其余应用。让我们创建src/containers/Root.js 文件:

`touch src/containers/Root.js`

目前, 我们将在这里使用一个占位符组件, 但我们将在讨论存储时替换此内容。现在, 让我们导出 _一些东西_:

import React from 'react';
import App from './App';

const Root = (props) => {
  return (
    <App />
  );
}

export default Root;

最后, 让我们更新的路由, 我们渲染我们的应用在src/index.js文件使用我们的新的Root 容器, 而不是它以前使用的App

import React from 'react';
import ReactDOM from 'react-dom';
import Root from './containers/Root';
import './index.css';

ReactDOM.render(
  <Root />,
  document.getElementById('root')
);

添加Redux

现在有了一个坚实的应用结构就位, 我们可以开始添加Redux。我们将采取的步骤以配合一些我们将建立的大多数应用的Redux结构通常都是相同的。我们需要:

  1. 写一个根归约器

  2. 写 actionCreators

  3. 配置存储、rootReducer和应用

  4. 将视图连接到 actionCreators

我们故意保持这个高层次的介绍短一些, 所以坚持一下, 这一切都将在短期内变得更有意义。

让我们设置允许我们添加Redux的结构。我们将在src/redux 目录中完成几乎所有的工作。让我们创建该目录。

mkdir -p src/redux
touch src/redux/configureStore.js
touch src/redux/reducers.js

首先我们先创建归约器。虽然它听起来很复杂, 但是归约器实际上是相当直接的, 有一定的经验。归约器仅是 字面 函数。它的唯一责任是返回一个 next 状态的表示。

在Redux模式, 不像flux, 我们只在 整个 应用处理 一个 全局存储的。这使得事情变得更容易处理, 因为有一个地方的数据, 我们的应用生活。

root 归约器函数负责返回应用当前全局状态的表示形式。当我们在存储上发送操作时, 将使用应用的当前状态和导致状态更新的操作来调用此归约器函数。

让我们在一个文件中建立我们的根归约器在src/redux/reducers.js.。

// Initial (starting) state
const initialState = {
  currentTime: new Date().toString()
}

// Our root reducer starts with the initial state
// and must return a representation of the next state
const rootReducer = (state = initialState, action) => {
  return state;
}

export default rootReducer

I在函数中, 我们将第一个参数定义为初始状态 (第一次运行时, 不带参数调用rootReducer , 因此它总是在第一次运行时返回 initialState )。

这就是现在的 rootReducer。就像现在这样, state的价值总是和 initialState 一样。在我们的例子中, 这意味着我们的数据树有一个单一的currentTime键。

什么是动作?

这里的第二个参数是从存储区发送的操作。我们很快就会回来。现在,让我们来看看动作。

最起码, 一个动作 必须 包括一个type 键。type 键可以是我们想要的任何值, 但它必须存在。例如, 在我们的应用中, 我们将偶尔发送一项操作, 我们想告诉存储获取 最新 当前时间。我们可以将此操作称为FETCH_NEW_TIME 的字符串值。

我们可能从我们的存储发送的处理此更新的操作如下:

{
  type: 'FETCH_NEW_TIME'
}

正如我们将通过键入这个字符串很多, 我们希望避免可能的拼写错误的地方, 它是常见的创建一个types.js 文件, 将操作类型导出为常量。让我们遵循这个约定, 创建一个src/redux/types.js 文件:

`export const FETCH_NEW_TIME = 'FETCH_NEW_TIME';`

我们将从types.js 文件中引用它, 而不是使用'FETCH_NEW_TIME' 的硬编码字符串调用该操作:

import * as types from './types';

{
  type: types.FETCH_NEW_TIME,
}

当我们想发送数据与我们的动作, 我们可以添加任何键, 我们想我们的动作。我们通常会看到这个所谓的payload 有效载荷, 但它可以被称为任何东西。这是一个公约, 调用附加信息的payload

我们的FETCH_NEW_TIME 动作将发送一个有效载荷与新的当前时间。因为我们想发送一个 序列化 值与我们的动作, 我们将发送新的当前时间的字符串值。

{
  type: types.FETCH_NEW_TIME,
  payload: new Date().toString() // Any serializable value
}

回到我们的归约器, 我们可以检查的动作类型, 并采取适当的步骤, 创建下一个状态。在我们的情况下, 我们只储存payload。如果操作的typeFETCH_NEW_TIME, 我们将返回新的 currentTime (从我们的操作有效负载) 和其他状态 (使用 ES6 扩展语法):

export const rootReducer = (state = initialState, action) => {
  switch(action.type) {
    case types.FETCH_NEW_TIME:
      return { ...state, currentTime: action.payload}
    default:
      return state;
  }
}

请记住, 归约 必须 返回一个状态, 所以在默认情况下, _最起码_确保返回当前状态。

保持轻型化

由于归约器的功能每次调度一个动作, 我们要确保这些功能是尽可能简单和快速。我们不希望他们造成任何副作用或有太多的延迟。

我们将处理动作创造者中归约器的副作用。

在我们看动作创造者 (以及为什么我们称他们为动作创造者) 之前, 让我们把我们的存储与我们的应用挂钩。

我们将使用 react-redux 包将我们的视图连接到我们的redux存储。让我们确保使用npm安装此包:

npm install --save react-redux

将存储与视图挂钩

react-redux 包输出一个名为 Provider 的组件。Provider 组件使存储可以使用我们的应用中的所有容器组件, 而无需我们每次都需要手动传递它。

Provider 组件期望一个store 的属性, 它期望是一个有效的redux存储, 所以我们的应用没有错误运行之前,我们需要完成一个configureStore 功能,。现在, 让我们在应用中连接Provider 组件。我们将通过更新我们以前创建的包装Root 组件来使用Provider 组件来完成此项。

import { Provider } from 'react-redux';
  // ...
const Root = (props) => {
  // ...

  return (
    <Provider store={store}>
      <App />
    </Provider>
  );
}

请注意, 我们正在将store 值发送到Provider 组件, 但我们还没有创建该存储。现在我们来解决这个问题。

配置存储

为了创建一个存储, 我们将使用新的src/redux/configureStore.js 来导出将负责创建存储的函数。

我们如何创建一个存储?

redux包输出一个叫做 createStore 的函数, 它将为我们创建实际的存储区, 因此, 让我们打开 src/redux/configureStore.js 文件并导出一个称为configureStore()的函数 (我们将很快定义), 并导入createStore` 帮助器:

import {createStore} from 'redux';
  // ...
export const configureStore = () => {
  // ...
}
  // ...
export default configureStore;

实际上我们在我们的存储还没有返回任何东西, 所以让我们实际创建的redux 存储使用 createStore 的功能, 我们从redux导入:

import {createStore} from 'redux';

export const configureStore = () => {
  const store = createStore();

  return store;
}

export default configureStore;

如果我们在浏览器中加载我们的页面, 我们将看到我们有一个巨大的错误: 没有页面得到渲染。

redux给我们的错误告诉我们, 我们存储里没有归约器。如果没有归约器, 它将不知道如何处理动作或如何创建状态等。为了越过这个错误, 我们需要参考我们创建的 rootReducer。

createStore 函数要求我们将 rootReducer 作为第一个参数来传递。它还希望将初始状态作为第二个参数传递。我们将从我们创建的reducers.js 文件中导入这两个值。

import { rootReducer, initialState } from './reducers'
  // ...
export const configureStore = () => {
  const store = createStore(
    rootReducer, // root reducer
    initialState, // our initialState
  );

  return store;
}

现在, 让我们通过调用 configureStore() 函数创建store 的实例来更新我们的Root.js 文件。

const Root = (props) => {
  const store = configureStore();

  return (
    <Provider store={store}>
      <App />
    </Provider>
  );
}

连接视图 (续)

我们的应用中的一切都设置为使用redux, 而无需 多开销。redux 提供的一个更方便的方法是使用react-redux包导出的connect() 函数, 将状态树的片断 绑定 到不同的组件。

connect() 函数返回一个函数, 它期望第一参数是一个组件。这通常叫做高阶组件。

connect() 函数要求我们在函数中至少传递一个参数 (但通常我们会传递两个)。它所期望的第一个参数是一个函数, 它将被称为state 并期望一个返回的对象将数据连接到视图。让我们看看我们是否能在代码中揭开这种行为的神秘面纱。

我们将这个函数称为mapStateToProps 函数。因为它的责任是将状态映射到与组件的原始props合并的对象。

让我们在src/views/Home.js 中创建 home 视图, 并使用此connect() 函数来绑定currentTime 在我们的状态树中的值。

import { connect } from 'react-redux';
  // ...
const mapStateToProps = state => {
  return {
    currentTime: state.currentTime
  }
}
export default connect(
  mapStateToProps
)(Home);

connect() 函数 自动 将函数的第一个参数中的任何键传递为Home 组件的props

在我们的演示案例中, Home 组件中的currentTime属性将映射到currentTime中的状态树键。让我们更新 Home 组件, 以显示currentTime中的值:

const Home = (props) => {
  return (
    <div className="home">
      <h1>Welcome home!</h1>
      <p>Current time: {props.currentTime}</p>
    </div>
  );
}

虽然这个演示不是很有趣, 它表明我们有我们的Redux 应用建立了我们的 data 致力于全局状态和我们的视图组件映射数据。

明天, 我们将开始通过操作创建者来触发我们的全局状态的更新, 并通过将多个redux模块组合在一起来工作。


不想成熟的大叔
882 声望526 粉丝

为学习前端开发不再枯燥、困难和迷茫而努力。