使用React&Redux打造书店I:React Redux Flow

At some point in your complex React project, you are going to need a state management library. Redux is a good choice because of it's simplicity and centralized data management. This piece is a practical approach to the fundamentals of Redux in building React application for managing a book store.

在您复杂的React项目中的某个时候,您将需要一个状态管理库。 Redux的简单性和集中式数据管理使其成为一个不错的选择。 这是在构建用于管理书店的React应用程序时基于Redux基础的实用方法。

If you are unfamiliar with the Redux idea/philosophy, Carly Kubacak previous article will prepare you for this so I suggest you have a look at it first.

如果您不熟悉Redux的想法/哲学,Carly Kubacak的前一篇文章将为您做好准备,因此,建议您首先了解一下。

何时使用Redux ( When to Use Redux )

We have been writing about React with consumable examples without having to seek the assistant of a state management utility. So why now?

我们一直在用可消耗的示例来编写React ,而不必寻求状态管理实用程序的帮助。 那为什么现在呢?

Pete Hunt said if you ever doubt if you need Redux or not then you do not need it. On another hand, if your app is complex and fall into any of the following, then you should consider Redux:

皮特·亨特(Pete Hunt)说,如果您不确定是否需要Redux,那么就不需要它。 另一方面,如果您的应用程序很复杂并且属于以下任何一种情况,那么您应该考虑使用Redux:

If you ever doubt if you need Redux or not then you do not need it. - Pete Hunt

如果您不确定是否需要Redux,则不需要它。 - 皮特·亨特

  • Non-hierarchical data structure

    非分层数据结构
  • Multiple events and actions

    多个事件和动作
  • Complex component interaction

    复杂组件交互

弹弓样板 ( Boilerplate with Slingshot )

DRY is my favorite principle in software engineering and I bet you like the idea too. For that reason, we are not going to write boilerplate code to get started, rather, we will use an existing solution.

DRY是我在软件工程中最喜欢的原则,我敢打赌您也喜欢这个想法。 因此,我们不会编写样板代码来开始使用,而是将使用现有的解决方案。

Cory House's Slingshot is awesome and covers everything you will need in a React-Redux app including linting, testing, bundling, etc. It is also has a high acceptance in the community with over 4k stars on GitHub.

Cory House的Slingshot非常棒,涵盖了React-Redux应用程序中所需的一切,包括棉绒,测试,捆绑等。它在社区中也获得了高度认可,在GitHub上有超过4k星。

Clone Slingshot to your favorite working directory with the following:

使用以下命令将弹弓克隆到您喜欢的工作目录:

git clone https://github.com/coryhouse/react-slingshot book-store

We need to prepare the cloned repository by installing dependencies and updating the package.json. Cory included an npm command to do all that:

我们需要通过安装依赖项并更新package.json来准备克隆的存储库。 Cory包含一个npm命令来执行所有操作:

npm run setup

You can run the app now with

您现在可以使用

npm start -s

The -s flag reduces noise in the console as Webpack which is the bundler will throw a lot of details about bundling to the console and that can be noisy and ugly.

-s标志减少了控制台中的杂音,因为Webpack是捆绑程序,它将把很多关于捆绑到控制台的详细信息扔到控制台上,这可能很吵而且很丑。

We do not need the app in the application so we can just remove it with a single command:

我们不需要应用程序中的应用程序,因此只需使用一个命令即可将其删除:

npm run remove-demo

We are good to start building our app.

我们很高兴开始构建我们的应用程序。

与Redux Flow互动 ( React with Redux Flow )

Before we dig deep into building our application, let's have a shallow approach by building a book manage page that creates a book with a form and lists the books. It's always important to keep the following steps in mind so you can always refer to it when building a React app with Redux:

在深入研究构建应用程序之前,让我们采用一种浅谈的方法,即建立一个图书管理页面,该页面创建具有表单的图书并列出图书。 务必牢记以下步骤,以便在使用Redux构建React应用时始终可以参考它:

  1. Create Components

    创建组件
  2. Create relevant actions

    建立相关动作
  3. Create reducers for the actions

    为动作创建减速器
  4. Combine all reducers

    合并所有减速器
  5. Configure store with createStore

    使用createStore配置商店
  6. Provide store to root component

    提供存储到根组件
  7. Connect container to redux with connect()

    使用connect()将容器连接到Redux

This tutorial is multi-part. What we will cover in this part is getting comfortable with this flow and then in the following parts we will add more complexity.

本教程分为多个部分。 我们将在本部分中介绍的是如何适应这种流程,然后在以下部分中,我们将增加更多的复杂性。

We will make some of the pages available using React Router but leave a dumb text in them. We will just be making use of the manage book page.

我们将使用React Router使某些页面可用,但在其中留下一个哑巴的文本。 我们将仅使用管理书页。

If you are unfamiliar with the difference between UI/Presentation components and Container components, I suggest you read our previous post.

如果您不熟悉UI / Presentation组件和Container组件之间的区别,建议您阅读我们以前的文章

1.创建组件(带有路线) (1. Create Components (with routes))

Let us create 3 components: home, about and manage page components. We will put these components together to make a SPA using React Router which we covered in full details in a previous post

让我们创建3个组件:主页,关于和管理页面组件。 我们将这些组件放在一起使用React Router制作SPA,我们在上一篇文章中详细介绍了它

Home Page: Create a file file named HomePage.js in ./src/components/common with the following:

主页 :在./src/components/common创建一个名为HomePage.js的文件,其内容如下:

// ./src/components/common/HomePage.js
import React from 'react';

const Home = () => {
  return (
    <div>
      <h1>Home Page</h1>

      <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. A aliquam architecto at exercitationem ipsa iste molestiae nobis odit! Error quo reprehenderit velit! Aperiam eius non odio optio, perspiciatis suscipit vel?</p>
    </div>
  );
};

export default Home;

About Page: Just like the home page but with a slightly different content:

关于页面 :与首页类似,但内容略有不同:

// ./src/components/common/AboutPage.js
import React from 'react';

const About = () => {
  return (
    <div>
      <h1>About Page</h1>

      <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. A aliquam architecto at exercitationem ipsa iste molestiae nobis odit! Error quo reprehenderit velit! Aperiam eius non odio optio, perspiciatis suscipit vel?</p>
    </div>
  );
};

export default About;

Book Page: This page will contain everything for creating and listing books. We won't abstract Ui components for now. We go against good practice and put all our codes here then with time refactor. This is just for clarity.

图书页面 :此页面将包含用于创建和列出图书的所有内容。 我们暂时不会抽象Ui组件。 我们违背良好实践,将所有代码放在此处,然后进行时间重构。 这只是为了清楚起见。

// ./src/components/book/BookPage.js
import React from 'react';

class Book extends React.Component{
  constructor(props){
    // Pass props back to parent
    super(props);
  }

  // Submit book handler
  submitBook(input){
    alert('Submitted')
  }

  render(){
    // Title input tracker
    let titleInput;
    // return JSX
    return(
      <div>
        <h3>Books</h3>
        <ul>
          {/* Traverse books array  */}
          {this.props.books.map((b, i) => <li key={i}>{b.title}</li> )}
        </ul>
        <div>
          <h3>Books Form</h3>
          <form onSubmit={e => {
            // Prevent request
            e.preventDefault();
            // Assemble inputs
            var input = {title: titleInput.value};
            // Call handler
            this.submitBook(input);
            // Reset form
            e.target.reset();
          }}>
            <input type="text" name="title" ref={node => titleInput = node}/>
            <input type="submit" />
          </form>
        </div>
      </div>
    )
  }
}

export default Book;

Root: We will create a root component now to house each of the other page components. React Router will also come into play here as we will use the Link component for navigation purposes:

:现在我们将创建一个根组件来容纳其他页面组件。 React Router也将在这里发挥作用,因为我们将使用Link组件进行导航:

// ./src/components/App.js
import React  from 'react';
import {Link} from 'react-router';

const App = (props) => {
  return (
    <div className="container">
      <nav className="navbar navbar-default">
        <div className="container-fluid">
          <div className="navbar-header">
            <a className="navbar-brand" href="#">Scotch Books</a>
          </div>
          <div className="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul className="nav navbar-nav">
              <li><Link to="/">Home</Link></li>
              <li><Link to="/about">About</Link></li>
              <li><Link to="/books">Book</Link></li>
              <li><Link to="/cart">Cart</Link></li>
            </ul>
          </div>
        </div>
      </nav>
      {/* Each smaller components */}
      {props.children}
    </div>
  );
};

export default App

We are using a functional component here which is one of the ways to create a React component. Passing the children down is a good way to inject the child components as determined by the router.

我们在这里使用功能组件,这是创建React组件的方法之一 。 使子代向下传递是注入由路由器确定的子代组件的好方法。

Routes: Let's now create component routes for this application. The file that will contain the routes can be at the root of your project if the project is a small one or you can split them up if you prefer to:

路线 :现在让我们为此应用程序创建组件路线。 如果项目很小,则包含路线的文件可以位于项目的根目录;如果您愿意,可以将其拆分:

// ./src/routes.js
import React  from 'react';
import {Route, IndexRoute} from 'react-router';
import Home from './components/common/HomePage'
import About from './components/common/AboutPage'
import Book from './components/book/BookPage'
import App from './components/App'

export default (
  <Route path="/" component={App}>
    <IndexRoute component={Home}></IndexRoute>
    <Route path="/about" component={About}></Route>
    <Route path="/books" component={Book}></Route>
  </Route>
);

You can see how the parent route is using App as it's component so all the children routes' component will be rendered where props.children is found in App component.

您可以看到父路线如何使用App作为其组件,因此所有子路线的组件都将在App组件中的props.children处呈现。

Entry: The entry point will render the application with it's routes to the DOM using ReactDOM's render method:

Entry :入口点将使用ReactDOM的render方法来渲染应用程序及其到DOM的路由:

// ./src/index.js

// Babel polyfill will emulate a full
// ES2015 environemnt so we can enjoy goodies like
// Promises
import 'babel-polyfill';
import React from 'react';
import { render } from 'react-dom';
import { Router, browserHistory } from 'react-router';
import routes from './routes';
import '../node_modules/bootstrap/dist/css/bootstrap.min.css';

render(
  <Router routes={routes} history={browserHistory} />,
  document.getElementById('app')
);

We are passing all the routes to the router as a property which is one way of defining Router's routes. As a reminder, if these routing terms are strange to you, I recommend you read a post on React Router.

我们将所有路由作为属性传递给路由器,这是定义路由器路由的一种方法。 提醒一下,如果您对这些路由术语感到陌生,建议您阅读React Router上的文章

At this point we are setup but our app will run with errors because the books props passed on to the Bookpage component is undefined. Let's keep moving though.

至此,我们已经安装Bookpage但由于传递到Bookpage组件的book道具是未定义的,因此我们的应用程序将运行错误。 让我们继续前进。

2.行动 (2. Actions)

The next step as listed above is to identify relevant actions and create them. Actions are object payloads that are identified by a required property type. Action creators are methods that wrap and return the action object. At the moment, we just need an action which we will create in the actions folder in the src directory:

上面列出的下一步是识别相关动作并创建它们。 动作是由所需属性type标识的对象有效负载。 动作创建者是包装并返回动作对象的方法。 目前,我们只需要在src目录中的actions文件夹中创建的actions

// ./src/actions/bookActions.js
export const createBook = (book) => {
  // Return action
  return {
    // Unique identifier
    type: 'CREATE_BOOK',
    // Payload
    book: book
  }
};

Now this action is ready to be dispatched by the store but no rush yet. Let's create the reducer that updates the store first.

现在,该动作已准备好由商店分派,但还不急。 让我们创建一个reducer,该reducer首先更新商店。

3.减速器 (3. Reducers)

Reducers are used to update the state object in your store. Just like actions, your application can have multiple reducers. For now, we just need a single reducer:

Reducer用于更新商店中的状态对象。 就像动作一样,您的应用程序可以具有多个reducer。 现在,我们只需要一个reducer:

// ./src/reducers/bookReducers.js
export default (state = [], action) => {
  switch (action.type){
    // Check if action dispatched is
    // CREATE_BOOK and act on that
    case 'CREATE_BOOK':
        state.push(action.book);
    default:
          return state;
  }
};

When the store dispatched an action, all the reducers are called. So who do we know which action to act on? By using a Switch statement, we determine which action was dispatched and act on that.

当商店调度动作时,将调用所有的reducer。 那么我们知道谁该采取行动呢? 通过使用Switch语句,我们确定调度了哪个操作并对其进行操作。

There is a big problem though. Reducers must be pure functions, which means they can't mutate data. Our current implementation of the reducer is mutating the array:

不过有一个大问题。 Reducer必须是纯函数,这意味着它们不能对数据进行突变。 我们目前对reducer的实现是对数组进行变异:

// ./src/reducers/bookReducers.js
// ...
state.push(action.book);
// ...

How do we make an update without mutating? The answer is to create another array of data and update it's content with the previous state and that changes made:

我们如何进行更新而不会发生变异? 答案是创建另一个数据数组,并使用先前的状态以及所做的更改来更新其内容:

// ./src/reducers/bookReducers.js
export default (state = [], action) => {
  switch (action.type){
    case 'CREATE_BOOK':
        return [
          ...state,
          Object.assign({}, action.book)
        ];
    default:
          return state;
  }
};

The spread operator just pours out the content on the array into the new array.

传播运算符只是将数组中的内容倒入新数组中。

4.组合减速器 (4. Combine Reducers)

I mentioned in the previous step that we could have as much reducers as we want but unlike actions, Reducers are not independent and can't standalone. The have to be put together and passed as one to the store. The act of putting multiple reducers together is known as reducer combination and the combined reducer is the root reducer.

我在上一步中提到过,我们可以拥有尽可能多的reducer,但是与action不同,Reducer不是独立的,不能独立运行。 必须将它们放在一起并作为一个整体传递给商店。 将多个异径管放在一起的动作称为异径管组合 ,组合异径管是根异径管

It's ideal to combine reducers at the root of the reducers' folder:

理想的是在化简文件夹的根目录下合并化简:

// ./src/reducers/index.js
import { combineReducers } from 'redux';
import books from './bookReducers'

export default combineReducers({
  books: books,
  // More reducers if there are
  // can go here
});

The combination is done with combineReducers() from the Redux library.

组合是通过Redux库中的combineReducers()完成的。

5.配置存储 (5. Configure Store)

The next step is shifting focus from reducers to store. This is the time to create your store and configure it with the root reducer, initial state and middleware if any:

下一步是将重点从减速器转移到存储。 现在是时候创建您的商店并使用root reducer,初始状态和中间件(如果有)对其进行配置:

// ./src/store/configureStore.js
import {createStore} from 'redux';
import rootReducer from '../reducers';

export default function configureStore(initialState) {
  return createStore(rootReducer, initialState);
}

We do not need any middleware now so we just leave that out. The createStore method from redux is used to create the store. The createStore method is wrapped in an exported function configureStore which we will later use to configure provider.

我们现在不需要任何中间件,因此我们将其省略。 reduxcreateStore方法用于创建商店。 createStore方法包装在导出的函数configureStore ,我们稍后将使用它配置提供程序。

6.提供商店 (6. Provide Store)

The Redux's store API includes (but not all):

Redux的商店API包括(但不是全部):

/* Dispatches actions */
store.dispatch()
/* Listens to dispatched actions */
store.subscribe()
/* Get state from store */
store.getState()

It is very inconveniencing to have to use this methods all around your react components. Dan, the founder of Redux built another library react-redux to help remedy this problem. All you need to do is import the Provider component from react-redux and wrap your entry point component with it:

必须在您的React组件周围使用此方法非常不便。 Redux的创始人Dan创建了另一个库react-redux来帮助解决此问题。 您需要做的就是从react-redux导入Provider组件,并用它包装您的入口点组件:

// ./src/index.js
import 'babel-polyfill';
import React from 'react';
import { Provider } from 'react-redux';
import { render } from 'react-dom';
import { Router, browserHistory } from 'react-router';
import routes from './routes';
import '../node_modules/bootstrap/dist/css/bootstrap.min.css';

import configureStore from './store/configureStore';

const store = configureStore();

render(
  <Provider store={store}>
    <Router routes={routes} history={browserHistory} />
  </Provider>,
  document.getElementById('app')
);

Not only do we wrap with Provider, we can now provide the store to the Provider component that will in turn give the descendants of the this entry component access to the store.

我们不仅可以使用Provider进行包装,而且现在还可以将store提供给Provider组件,从而使该入口组件的后代可以访问存储。

7.连接 (7. Connect)

Wrapping our entry component with Provider does not mean we can go home happy. There is still a little job to be done. We need to pass down the states to our components props, same goes with the actions.

Provider包装我们的输入组件并不意味着我们可以开心回家。 还有一点工作要做。 我们需要将状态传递给我们的组件道具,动作也是如此。

Best practice demands that we do this in container components while convention demands that we us mapStateToProps for states and mapDispatchToProps for actions.

最佳做法要求我们在容器组件中执行此操作,而约定则要求我们将mapStateToProps用于状态,将mapDispatchToProps用于操作。

Let's update the BookPage container component and see how the flow gets completed:

让我们更新BookPage容器组件,看看流程如何完成:

// ./src/components/book/BookPage.js
import React from 'react';
import { connect } from 'react-redux';
import * as bookActions from '../../actions/bookActions';

class Book extends React.Component{
  constructor(props){
    super(props);
  }

  submitBook(input){
    this.props.createBook(input);
  }

  render(){
    let titleInput;
    return(
      <div>
        <h3>Books</h3>
        <ul>
          {this.props.books.map((b, i) => <li key={i}>{b.title}</li> )}
        </ul>
        <div>
          <h3>Books Form</h3>
          <form onSubmit={e => {
            e.preventDefault();
            var input = {title: titleInput.value};
            this.submitBook(input);
            e.target.reset();
          }}>
            <input type="text" name="title" ref={node => titleInput = node}/>
            <input type="submit" />
          </form>
        </div>
      </div>
    )
  }
}

// Maps state from store to props
const mapStateToProps = (state, ownProps) => {
  return {
    // You can now say this.props.books
    books: state.books
  }
};

// Maps actions to props
const mapDispatchToProps = (dispatch) => {
  return {
  // You can now say this.props.createBook
    createBook: book => dispatch(bookActions.createBook(book))
  }
};

// Use connect to put them together
export default connect(mapStateToProps, mapDispatchToProps)(Book);

mapStateToProps now makes it possible to access the books state with this,props.books which we were already trying to do in the component but was shy to run because we were going to see red in the console.

现在, mapStateToProps使得可以使用我们已经在组件中尝试过的this,props.books来访问图书状态,但由于在控制台中会看到红色,因此无法运行。

mapDispatchToProps also returns an object for the respective dispatched actions. The values are functions which will be called when the actions are dispatched. The value expects a payload which you can pass in as well when dispatching.

mapDispatchToProps还为相应的分派动作返回一个对象。 这些值是在分派动作时将调用的函数。 该值需要一个有效载荷,您也可以在分派时传递该有效载荷。

The connect method now takes in these 2 functions and returns another functions. The returned function is now passed in the container component.

现在, connect方法接受这两个函数并返回另一个函数。 现在,将返回的函数传递到容器组件中。

Now you can run the app with:

现在,您可以通过以下方式运行该应用程序:

npm start -s

We should have a working demo like the one in the image below:

我们应该有一个工作示例,如下图所示:

Book Page

Home Page

About Page

下一个... (Up Next...)

We just had a quick look on React with Redux but this can make a real app. In the next part of this tutorial, we will try to draw closer to something real like making async requests with redux-thunk

我们只是快速浏览了React with Redux,但这可以使它成为一个真正的应用程序。 在本教程的下一部分中,我们将尝试更接近真实的东西,例如使用redux-thunk发出异步请求

翻译自: https://scotch.io/tutorials/build-a-bookshop-with-react-redux-i-react-redux-flow

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值