美文网首页Redux思想Web前端之路让前端飞
翻译|A Dummy’s Guide to Redux and

翻译|A Dummy’s Guide to Redux and

作者: smartphp | 来源:发表于2017-04-16 14:16 被阅读83次

    title: 翻译|A Dummy’s Guide to Redux and Thunk in React
    date: 2017-04-15 21:36:07
    categories: 翻译
    tags: Redux


    Redux和Thunk的使用傻瓜教程

    原文参见
    Github repo
    强烈推荐这篇文章.

    如果你和我一样,读了Redux的文档,看了Dan的录像,Wes的课程,仍然不能抓住怎么使用Redux的核心,希望这个傻瓜教程能够帮到你.
    在真正实施之前,我做了一些尝试,所以我决定写一点从已经存在的使用fetch JSON数据的app一步步转变为使用Redux和Redux Thunk的app.如果你不知道Thunk是什么,也不要担心,我们会在”Redux 之路”部分来使用它执行函数的异步调用.
    这个教程需要你对React和ES6/2015有基本的掌握.

    非Redux方法

    components/ItemList.js中创建一个React组件,用于fetch和显示items列表.

    罗列一下基础组件

    首先我们使用包含各种items的state配置一个静态的组件,2 个boolean state分别用于根据loading和error来渲染出各自的单独显示组件.

     import React, { Component } from 'react';
    class ItemList extends Component {
      constructor() {
          super();
          this.state = {
              items: [//items在列表中现实的内容
                  {
                      id: 1,
                      label: 'List item 1'
                  },
                  {
                      id: 2,
                      label: 'List item 2'
                  },
                  {
                      id: 3,
                      label: 'List item 3'
                  },
                  {
                      id: 4,
                      label: 'List item 4'
                  }
              ],
              hasErrored: false, //网络请求错误的状态
              isLoading: false   //网络请求中的状态
          };
      }
      render() {
          if (this.state.hasErrored) {//根据Errored的状态决
          //定是否加载这个组件,网络请求错误时,false=>true
              return <p>Sorry! There was an error loading the items</p>;
          }
          if (this.state.isLoading) {
          //网络请求中的组件,发出请求时,false=>true
              return <p>Loading…</p>;
          }
          return (
              <ul>
                  {this.state.items.map((item) => (
                      <li key={item.id}>
                          {item.label}
                      </li>
                  ))}
              </ul>
          );
      }
    }
    export default ItemList;
    

    可能看起来还不是特别能说明问题,但是已经是一个好的开端了.
    渲染的时候,组件输出四个items列表,但是如果你把isLoadinghasError的state由false改为false的时候,对应的<p></p>就会显示出来.(注意每个组件都是return出来的,每次只显示一个).

    改为动态值

    直接编码items对于组件来说不是特别有用,所以最好从JSON API来fetch items数据,如果这样做的话,我们就可以把isLoadinghasError改为合适的状态.
    响应值和我们直接编码是一样,但是在实际生产中,你可能会拉回一个图书畅销榜的列表,最新的blog帖子或者其他app中需要的内容.
    为了fetch items,我们将使用合适的Fetch API.Fetch使得执行请求比传统的XMLHttpRequest更容易,并且返回的是响应值的promise对象(这一点对于Thunk很重要).Fetch并不是在所有的浏览器中都可以使用,所以你需要在项目中添加依赖项.

     npm install whatwg-fetch --save
    

    转变实际上相当简单.

    • 首先把items的初始化state设置为空数组
    • 现在我们添加一个方法fetch数据,同时还要设定loading和error的状态.
    fetchData(url) {//fetch的包装方法
      //进入函数首先设定isLoading state false=>true
      this.setState({ isLoading: true });
      fetch(url)
          .then((response) => {//返回promise独享
              if (!response.ok) {
                  throw Error(response.statusText);
              }
              //不管返回的数据是什么,只要返回数据
              //就修改isLoading state true=>false
              this.setState({ isLoading: false });
              return response;
          })
          .then((response) => response.json())
          .then((items) => this.setState({ items })) 
          // ES6 property value shorthand for { items: items }
          .catch(() => this.setState({ hasErrored: true }));//返回数据的解析为json,如果捕获到错误就hasErrored:
          // false=>true
    }
    
    • 函数写完以后,在组件加载的时候就调用函数
     componentDidMount() {
     this.fetchData('http://      5826ed963900d612000138bd.mockapi.io/items');
    }
    

    完成以后,代码如下

     class ItemList extends Component {
       constructor() {
           this.state = {
               items: [],
           };
       }
       fetchData(url) {
           this.setState({ isLoading: true });
           fetch(url)
               .then((response) => {
                   if (!response.ok) {
                       throw Error(response.statusText);
                   }
                   this.setState({ isLoading: false });
                   return response;
               })
               .then((response) => response.json())
               .then((items) => this.setState({ items }))
               .catch(() => this.setState({ hasErrored: true }));
       }
       componentDidMount() {
           this.fetchData('http://5826ed963900d612000138bd.mockapi.io/items');
       }
       render() {
       }
    }
    
    

    差不多了,组件现在从REST API fetch items数据,在4个items到达之前,你希望看”Loading...”出现提示.如果URL不能返回数据,你应该看到error 的信息.

    然而(译注:讨厌的然而),在现实中,组件不应该包含具体的fetch逻辑,data也不应该储存在组件的state中,所以Redux实时的出现了.

    转变到Redux

    需要添加Redux, React Redux 和Redux Thunk作为依赖项.

     npm install redux react-redux redux-thunk --save
    

    理解Redux

    有几个Redux的核心原理,我们必须要理解(译注:话很简单,但是要在大脑里构建出Redux的工作流程是有花很多时间的和精力的).

    1. Redux中有一个全局state对象来管理整个应用的state.在篇文章中,全局对象就是我们组件的初始化对象.
    2. 唯一能改变state的是触发一个action,action是一个描述state应该怎么改变的对象,Action Creators可以被dispatched的函数,触发一个变化,执行的内容是返回一个action
    3. 当一个action被dispatch以后,Reducer执行根据action的内容实际改变state对象,如果action没有找到匹配项,就会返回默认的state.
    4. Reducers是纯函数,他们不能有任何的异步操作和mutate-必须要返回一个修改的copy.
    5. 单个的Reducer可以被combine为一个单一的rootReducer,从而创建一个离散的state聚合体.
    6. Store是把action和reducer组织到一起的工具,他包含了rootReducer代表的状态,中间件,允许你执行实际的dispatchactions
    7. 为了在React中使用Redux,<Provider />组件包装整个应用,传递store到子代组件们.

    设计我们的state

    从我们现在已有的代码里,可以知道我们的state需要3个属性(properties):items,hasErrored,isLoading,这个三个属性相应的需要三个独立的actions.

    现在,这里讲讲为什么Action Creator和Action是不同的,他们也不是1:1的关系:我们需要第四个actiong creator来根据fetch data的不同状态调用其他三个action(creators).这第四个action creator几乎和我们原先的fetchData()一样,但是它不会直接的使用this.setState({isLoading:true})来设置状态,我们将dispatch一个action去做同样的事情:dispatch(isLoading(true)).

    创建actions

    在actions目录下创建itmes.js文件,其中包含我们的action creators.创建三个简单的actions.

        export function itemsHasErrored(bool) {
       return {
           type: 'ITEMS_HAS_ERRORED',
           hasErrored: bool
       };
    }
    export function itemsIsLoading(bool) {
       return {
           type: 'ITEMS_IS_LOADING',
           isLoading: bool
       };
    }
    export function itemsFetchDataSuccess(items) {
       return {
           type: 'ITEMS_FETCH_DATA_SUCCESS',
           items
       };
    }
       
         
        ```
    
    
    
      
    
    
    
        
    
       
      
    如前面提到的,action creators似能返回函数的函数,使用`export`输出单个action creators,便于在代码中使用.
       第二个action creators接受一个布尔值(true/false)最为参数,返回一个有意义的`type`和布尔值,分配合适的属性.
       第三个,`itemsFetchSuccess()`,当数据成功返回以后,传递数据作为`items`属性的值.通过ES6的魔术属性缩写,我们能够返回一个对象含有属性名叫做`items`,他的值是`items`的数组.
       
      (Note: that the value you use for type and the name of the other property that is returned is important, because you will re-use them in your reducers)这一句不知道怎么翻译.
      现在,我们有了三个actions,代表我们的状态,把原来的组件方法`fetchData`该给`itemFetchDaga()`action creator.
      默认情况下,Redux action creators是不支持异步actions的,像是fetching data的操作,所以这里我们使用Redux Thunk.Thunk允许你在action creator里返回一个函数代替实际的action.内部函数接受`dispatch`和`getState`作为参数,但是我们仅仅使用`dispatch`.
      实际的简单例子中五秒以后将会触发`itemHasErrored()`函数.
      
    
    export function errorAfterFiveSeconds() {
    // We return a function instead of an action object
    //dispatch作为参数传递给胖箭头函数
    return (dispatch) => {
        setTimeout(() => {
            // This function is able to dispatch other action creators
            dispatch(itemsHasErrored(true));
        }, 5000);
    };
    

    }

    现在我们知道thunk是什么了.编写`itemsFetchData()`.
    
    
    export function itemsFetchData(url) {
    return (dispatch) => {
        //已进入fetchdata,按顺序把isLoading state 由
        // false=>true
        dispatch(itemsIsLoading(true));
        //fetch执行实际的异步远程获取数据操作
        fetch(url) 
            .then((response) => {
                if (!response.ok) {//根据状态抛出错误
                    throw Error(response.statusText);
                }
                //isLoading又改为false,加载Loading组件
                dispatch(itemsIsLoading(false));
                return response;
            })
            .then((response) => response.json())
            .then((items) => dispatch(itemsFetchDataSuccess(items)))
            .catch(() => 
            dispatch(itemsHasErrored(true)));
            //捕获错误以后HasError的状态 false=>true
    };
    

    }

    
    ## 创建我们的reducers
    
    action定义好了以后,可以编写reducers接受actions,接着返回appliction的新状态(译注:实际上store中返回的对象都是一个新的对象,不是原对象的引用,这个就叫做immutable,facebook定义了一个immutable.js的技术实际是也是返回一个新的对象的硬拷贝,但是在原对象和修改对象之间共享了一部分内容,这一点有点微妙).
    注意:在Redux中,所有的reducers不考虑action,都会调用,所以即就是没有action被应用,你也必须要返回一个原来的定义的`state`.
    
    每一个reducer接收两个参数,之前的state和一个`action`对象.也可以使用ES6的属性来调用默认的参数设定到默认的`初始化state`.
    
    在每个reducer内部,使用`switch`申明来决定到底哪个`action.type`相匹配.如果是简单的reducer,可能没有必要使用`switch`,理论上使用`if/else`可能更快一点.
    
    如果`action.type`一点匹配,然后会返回和`action`相关的属性.和前面提到的一样,`type`和`action[属性名]`是在action creators里定义的.
    
    好啦,了解到这些内容,在`reducers/item.js`中创建items reducers
    

    export function itemsHasErrored(state = false, action) {
    switch (action.type) {
    case 'ITEMS_HAS_ERRORED':
    return action.hasErrored;
    default:
    return state;
    }
    }
    export function itemsIsLoading(state = false, action) {
    switch (action.type) {
    case 'ITEMS_IS_LOADING':
    return action.isLoading;
    default:
    return state;
    }
    }
    export function items(state = [], action) {
    switch (action.type) {
    case 'ITEMS_FETCH_DATA_SUCCESS':
    return action.items;
    default:
    return state;
    }
    }

    
    注意reducer根据结果store的state属性来命名,`action.type`没有必要想对应.前两个表达完整的意思,第三个`items()`就稍微有点不同.
    
    这是因为,可能会有很多条件返回`items`数组:有可能返回所有的数组,有可能在删除dispatch以后返回`items`的次级结构.或者所有的items都被删除了,会返回一个空数组.
    
    为了重新遍历,每一个reducer都会返回一个截然不同的state属性,不需要考虑reducer内部的条件到底有多少.刚开始花了我很长时间想明白这个问题.
    单个的reducers创建好了以后,我们需要把单个的reducer合并(combine)成一个`rootReducer`,创建单一对象.
    
    创建文件`reducers/index.js`
    
    

    import { combineReducers } from 'redux';
    import { items, itemsHasErrored, itemsIsLoading } from './items';
    //由于每个reducer返回的都是一个对象
    //所以这里的操作就是合并对象的操作,在underscore和loadsh
    //里面可以找到合并js对象的代码
    export default combineReducers({
    items,
    itemsHasErrored,
    itemsIsLoading
    });

    我们从`items`里导入每个reducers,使用redux的`combineReducers()`函数来合并输出单一对象(译注:所以每一个reducer返回的对象的属性名应该是唯一的,否则就覆盖了,前面的内容表达过这个意思)
    因为我们的reducer的名字和在store中使用的属性名一样,所以我们可以使用ES6的对象字面量.
    
    注意,我有意提到了reducer的前缀,所以当我们的application变得比较复杂的时候,不能出现全局性的`hasErrored`和`isLoading`属性.可以使用不同的error和loading state,所以前缀可以给你很大的灵活性.例如
    
    import { combineReducers } from 'redux';
    

    import { items, itemsHasErrored, itemsIsLoading } from './items';
    import { posts, postsHasErrored, postsIsLoading } from './posts';
    export default combineReducers({
    items,
    itemsHasErrored,
    itemsIsLoading,
    posts,
    postsHasErrored,
    postsIsLoading
    });

    
    替代方法是,可以在import的时候使用别名.但是我更愿意使用独一无二的名字.
    
    ## 配置store,注入到你的app中
    
    操作很直接,创建`store/configureStore.js`
    
    

    import { createStore, applyMiddleware } from 'redux';
    import thunk from 'redux-thunk';
    import rootReducer from '../reducers';
    export default function configureStore(initialState) {
    return createStore(
    rootReducer,
    initialState,
    applyMiddleware(thunk)
    );
    }

    
    现在在index.js中包含`<Provider />`组件,`configureStore`,配置`store`.包装app(`<ItemList />`),传递进去`store`和`props`.
    
    

    import React from 'react';
    import { render } from 'react-dom';
    import { Provider } from 'react-redux';
    import configureStore from './store/configureStore';
    import ItemList from './components/ItemList';
    const store = configureStore(); // You can also pass in an initialState here
    render(
    <Provider store={store}>
    <ItemList />
    </Provider>,
    document.getElementById('app')
    );

    
    我知道,其实花了很多努力才到了这一步,但是随着设置的完成,我们就可以使用配置来操纵我们的组件了(译注:这里是意译,组件又称为木偶组件,意思很清楚吧?谁是拿着吊线的人呢?就是redux).
    
    ## 把组件转化为使用Redux store和方法
    
    跳回到`components/ItemList.js`
    
    在顶部导入需要的部分
    

    import { connect } from 'react-redux';
    import { itemsFetchData } from '../actions/items';

    
    `connect`可以让组件链接到Redux的store,`itemsFetchData`是在开始写的action creator.我们仅仅需要导入actin creator.使用`dispatch`来触发actions.(译注:redux里面有很多内容其实是很基础的,例如这里,javascript的函数是一类对象,在js中函数是传引用的,所以函数名可以作为函数的引用,通过另一函数的参数来传递. 厉害 👍).
    在组件类定义好了以后,我们可以把Redux的state和action creator的dispatch映射到props上.
    创建一个函数,接受`state`返回一个props对象.在简单的组件中,可以把前缀去掉.
    
    

    const mapStateToProps = (state) => {
    return {
    items: state.items,
    hasErrored: state.itemsHasErrored,
    isLoading: state.itemsIsLoading
    };
    };

    
    接着我们需要另一函数在props中`dispatch`我们的`itemsFetchData()`函数.
    
    

    const mapDispatchToProps = (dispatch) => {
    return {
    fetchData: (url) => dispatch(itemsFetchData(url))
    };
    };

    
    还是把`item`前缀去掉.这里的fetchData函数接受一个`url`参数,返回
    派发调用`itemsFetchData(url)`操作.
    
    现在,`mapStatetoProps()`和`mapDispatchToProps()`还不能做什么事情,需要改变一下代码
    
    

    export default connect(mapStateToProps, mapDispatchToProps)(ItemList);

    `connect`函数把组件连接到Redux,同时映射了需要用到的props.
    
    最后一步是使用props替换掉state.
    
    * 删掉`constructor(){}`和`fetchData(){}`
    * 在`componentDidMount()`里把`this.fetchData()`改为`this.props.fetchData()`.
    * `this.state.x` 统一改为`this.props.X`.
    
    你的组件看起来应该是
    
    

    import React, { Component } from 'react';
    import { connect } from 'react-redux';
    import { itemsFetchData } from '../actions/items';
    class ItemList extends Component {
    componentDidMount() {
    this.props.fetchData('http://5826ed963900d612000138bd.mockapi.io/items');
    }
    render() {
    if (this.props.hasErrored) {
    return <p>Sorry! There was an error loading the items</p>;
    }
    if (this.props.isLoading) {
    return <p>Loading…</p>;
    }
    return (
    <ul>
    {this.props.items.map((item) => (
    <li key={item.id}>
    {item.label}
    </li>
    ))}
    </ul>
    );
    }
    }
    const mapStateToProps = (state) => {
    return {
    items: state.items,
    hasErrored: state.itemsHasErrored,
    isLoading: state.itemsIsLoading
    };
    };
    const mapDispatchToProps = (dispatch) => {
    return {
    fetchData: (url) => dispatch(itemsFetchData(url))
    };
    };
    export default connect(mapStateToProps, mapDispatchToProps)(ItemList);

    
    好了,application现在使用Redux和Redux thunk来fetch和显示数据.
    
    也不是太困难,是不是?
    
    你现在也是Redux大师了 :D
    
    
    ## 接下来做什么?
    
    我在github提交了每一步的代码.我希望你克隆和理解他,然后添加根据items的id删除item的功能.
    
    我没有真正提到Redux的内容是,state是immutable,意识是你不能修改他.所以在reducers中每次都要返回一个新的state,上面的三个reducers比较简单,刚刚好可以工作.但是如果是从items的数组中删除操作,你可能不熟悉.
    你不能再使用`Array.protype.splice()`来从数组中移除items.因为这样做会mutate之前的state.[Dan在视频中讲解了怎么从数组中删除一个元素](https://egghead.io/lessons/javascript-redux-avoiding-array-mutations-with-concat-slice-and-spread).如果你坚持要做,可以看看`delete-items`分支,可以找到解决办法.
    
    我真切的希望这篇文章讲明了Redux和Thunk的原理以及怎么把现有的React application转变到使用redux.写作过程巩固了我对Redux的理解(译注:翻译过程也巩固了我的理解),所以我很高兴这么做(译注:so do I 😁).
    
    
     下面有点不翻了。
     
     
     >我个人觉得这篇文章却似写的非常的好.强烈推荐.

    相关文章

      网友评论

        本文标题:翻译|A Dummy’s Guide to Redux and

        本文链接:https://www.haomeiwen.com/subject/xkeaattx.html