金沙棋牌官方平台

当前位置:金沙棋牌 > 金沙棋牌官方平台 > 学习总结

学习总结

来源:http://www.logblo.com 作者:金沙棋牌 时间:2019-11-29 12:48

State的不可变化带来的麻烦

在用Redux处理深度复杂的数据时会有一些麻烦。由于js的特性,我们知道当对一个对象进行复制时实际上是复制它的引用,除非你对这个对象进行深度复制。Redux要求你每次你返回的都是一个全新的State,而不是去修改它。这就要求我们要对原来的State进行深度复制。这往往带来复杂的操作(查找,合并)。一种简单的情况是通过扩展符号或者Object.assign来处理:

return { ...state, data: { ...state.data, id: 5 } }

1
2
3
4
5
6
7
return {
    ...state,
    data: {
        ...state.data,
        id: 5
    }
}

这种方式在处理简单的数据来说是比较方便的,但是如果遇到更深一级的数据结构时会显得很无力,而且代码会变得冗长。不仅仅如此,当我们要处理一个包含着对象的数组时,我们要怎么办才好呢?我想,除了深度复制数组然后修改新的数组之外大概没有其他的方法了吧?而且很重要的一点是,如果你对原来整个数组进行了复制,那么绑定了数据的UI会自动渲染,即使它们的数据没有发生变化,简单来说,就是你修改了某一条表格数据,但是界面上整个表格被重新渲染了:

const TablesSource = { query: 'tables', tableId: 10, data: [{ key: 11, name: '胡彦斌', age: 32,//我要修改这里,要复制整个数组后修改新的吗? address: '西湖区湖底公园1号' }, { key: 12, name: '胡彦祖', age: 42, address: '西湖区湖底公园1号' }] };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const TablesSource = {
    query: 'tables',
    tableId: 10,
    data: [{
        key: 11,
        name: '胡彦斌',
        age: 32,//我要修改这里,要复制整个数组后修改新的吗?
        address: '西湖区湖底公园1号'
    }, {
        key: 12,
        name: '胡彦祖',
        age: 42,
        address: '西湖区湖底公园1号'
    }]
};

在Redux官方文档中提到了一种解决方案,即范式化数据:概括起来就一句话:减少层级,唯一id索引,用后端建表的方法构建我们的数据结构。其中最重要原则无非是扁平化和关联性。最终我们需要将数据形式转化成以下格式:

{ "entities": { "bykey": { "11": { "key": 11, "name": "胡彦斌", "age": 32, "address": "西湖区湖底公园1号" }, "12": { "key": 12, "name": "胡彦祖", "age": 42, "address": "西湖区湖底公园1号" } }, "table": { "10": { "query": "tables", "tableId": 10, "data": [ 11, 12 ] } } }, "result": 10 }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
  "entities": {
    "bykey": {
      "11": {
        "key": 11,
        "name": "胡彦斌",
        "age": 32,
        "address": "西湖区湖底公园1号"
      },
      "12": {
        "key": 12,
        "name": "胡彦祖",
        "age": 42,
        "address": "西湖区湖底公园1号"
      }
    },
    "table": {
      "10": {
        "query": "tables",
        "tableId": 10,
        "data": [
          11,
          12
        ]
      }
    }
  },
  "result": 10
}

按照卤煮的理解,范式化数据无非就是给对象瘦瘦身,再深的层级,我们也尽量将它们扁平化,这样会减少我们对State的查找带来的性能消耗。然后是建立索引表,标识每组数据之间的联系。那么怎么样才能得到我们想要的数据呢?

import { createStore } from 'redux'

3.Reducer的拆分和合并

在实际项目中,reducer 很庞大,不易阅读管理,我们可以将reducer 拆分成小的函数,不同的函数对应处理不同的属性。然后将其合并成一个大的reducer

Reducer 提供了一个方法combineReducers方法来合并reducer.

const chatReducer = (state = defaultState, action = {}) => {
  return {
    chatLog: chatLog(state.chatLog, action),
    statusMessage: statusMessage(state.statusMessage, action),
    userName: userName(state.userName, action)
  }
};

import { combineReducers } from 'redux';

const chatReducer = combineReducers({
  chatLog,
  statusMessage,
  userName
})

export default todoApp;

你可以把所有子reducers 放在一个文件夹里,然后统一引入。

import { combineReducers } from 'redux'

import * as reducers from './reducers'

const reducer = combineReducers(reducers)

Redux和Thunk的使用傻瓜教程

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

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

Reselect方法使用指南

//缓存data里面的索引 const reNormalDataSource = (state, props) => state.app.entities.table['10'].data; //缓存bykey里面对得基础数据 const reNormal = (state, props) => state.app.entities.bykey; // 缓存计算结果 const createNormalTableData = createSelector([reNormalDataSource, reNormal], (keys, source) => keys.map(item => source[item])); //每次当mapStateToProps重新执行时,会储存上次计算的结果,它只会重新计算变化的数据,其他非相关变化不做计算 const mapStateToProps = (state, own) => ({source: createNormalTableData(state)});

1
2
3
4
5
6
7
8
//缓存data里面的索引
const reNormalDataSource = (state, props) => state.app.entities.table['10'].data;
//缓存bykey里面对得基础数据
const reNormal = (state, props) => state.app.entities.bykey;
// 缓存计算结果
const createNormalTableData = createSelector([reNormalDataSource, reNormal], (keys, source) => keys.map(item => source[item]));
//每次当mapStateToProps重新执行时,会储存上次计算的结果,它只会重新计算变化的数据,其他非相关变化不做计算
const mapStateToProps = (state, own) => ({source: createNormalTableData(state)});

我在这里做了个耍了点花样,你可以看到,我是按照table.data这个数组来查找界面业务数据的。这种操作可以使得我们只需要关心table.data这个简单的一维数组,在删除或者添加一条数据的时候显得尤为有用。

我们最后为了计算state,引用了dot-prop-immutable模块,他是immutable的扩展,对于数据计算非常高效。我接着使用了另外一个dot-prop-immutable-chain模块,它增加了dot-prop-immutable的链式用法。关于dot-prop-immutable的用法卤煮不再详细说明,在后面给出的例子中一眼就能看明白,而且官方文档上也有详细说明。下面我们通过一个表格增删查改来实际展示我们这次的解决方案。

金沙棋牌官方平台 1

import {normalize, schema} from 'normalizr'; import dotProp from 'dot-prop-immutable-chain'; const reducer = (state = normalizedData, action) => { switch(action.type) { //修改一条数据 case 'EDITOR': return dotProp(state).set(`entities.bykey.${action.key}.age`, action.age).value(); //添加一条数据 case 'ADD': const newId = UID++; return dotProp(state).set(`entities.bykey.${newId}`, Object.assign({}, model, {key: newId}))//添加一条新数据 .merge(`entities.table.10.data`, [newId]).value();//新数据的data中的引用 //删除一条数据 case 'DELETE': const index = state.entities.table['10'].data.indexOf(Number(action.key)); //可以看到,由于我们界面数据是根据data里面的项来决定的,因此我们只需要处理data这个简单的一维数组,而这显然要容易维护得多 return dotProp(state).delete(`entities.table.10.data.${index}`).value(); } return state; };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import {normalize, schema} from 'normalizr';
import dotProp from 'dot-prop-immutable-chain';
 
const reducer = (state = normalizedData, action) => {
    switch(action.type) {
        //修改一条数据
        case 'EDITOR':
            return dotProp(state).set(`entities.bykey.${action.key}.age`, action.age).value();
        //添加一条数据
        case 'ADD':
            const newId = UID++;
           return dotProp(state).set(`entities.bykey.${newId}`, Object.assign({}, model, {key: newId}))//添加一条新数据
               .merge(`entities.table.10.data`, [newId]).value();//新数据的data中的引用
        //删除一条数据  
        case 'DELETE':
            const index = state.entities.table['10'].data.indexOf(Number(action.key));
            //可以看到,由于我们界面数据是根据data里面的项来决定的,因此我们只需要处理data这个简单的一维数组,而这显然要容易维护得多
            return dotProp(state).delete(`entities.table.10.data.${index}`).value();
    }
    return state;
};

瞧,我们展示了整个reducer,相信它已经变得容易维护得多了,并且由于使用了范式化数据结构以及immutable的扩展模块,不仅仅提升了计算性能,减少界面的的渲染,而且还符合Redux的State不可修改的原则。

   mapDispatchToProps

1.Redux 设计理念

  Web 应用是一个状态机,视图与状态是一一对应的

  所有的状态,保存在一个对象里面

 


Redux进阶(一)

2018/08/26 · JavaScript · 1 评论 · Redux

原文出处: 卖烧烤夫斯基   

export { VisibleCreateUnit, VisibleCreateTask }

6. react+redux 的 simple 示例

原理图:

金沙棋牌官方平台 2

<div id="container"></div>

<script type="text/babel">

  //工厂Action

    var addTodoAction = function(text){

      return {

        type: 'add_todo',

        text: text

      }

    };


    var todoReducer = function(state,action){

      if(typeof state === 'undefined') return [];

      switch(action.type){

        case 'add_todo':

        return state.slice(0).concat({

          text: action.text,

          completed:false

        });

        default:

        return state;

      }

    };


    var store = redux.createStore(todoReducer);


    var App = React.createClass({

      //获取初始状态

      getInitialState: function(){

        return {

          items: store.getState()

        };

      },

      componentDidMount: function(){

        var unsubscribe = store.subscribe(this.onChange);

      },

      onChange: function(){

        this.setState({

          items: store.getState()

        });

      },

      handleAdd: function(){

        var input = ReactDOM.findDOMNode(this.refs.todo);

        var value = input.value.trim();

        if(value){

          store.dispatch(addTodoAction(value));

        }

        input.value = '';

      },

      render: function(){

        return(

          <div>

          <input ref="todo" type="text" placeholder="input todo type"/>

          <button onClick ={this.handleAdd}>Add</button>

          <ul>

          {

            this.state.items.map(function(item){

              return <li>{item.text}</li>

            })

          }

          </ul>

          </div>

        );

      }

    });

    ReactDOM.render(

      <App />,

      document.getElementById('container')

    )

  </script>

 

非Redux方法

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

参考资料

 Redux中文文档

把你的redux store 组织成数据库形式

normalizr在GitHub上的地址

reselect在GitHub上的地址

dot-prop-immutable在GitHub上的地址

1 赞 收藏 1 评论

金沙棋牌官方平台 3

return state - 1

4.中间件和异步操作

我们使用redux ,用户发出action,Reducer算出新的state,然后重新渲染界面。这里Reducer是立即算出state,立即响应的,同步执行的顺序。 

金沙棋牌官方平台,可是如果我们需要执行异步实现,Reducer执行完之后,自动执行呢? 这里就需要用到middleWare(中间件)。

中间件加在什么地方合适?我们来简单分析下。

  1. Reducer 是纯函数,用来计算state,相同的输入必然得到相同的输出,理论上纯函数不允许读写操作。

  2. View和state是一一对应,是state的呈现,没有处理能力。

  3. Action 是存放数据的对象,即消息的载体,被触发操作。

最后发现,只有加在store.dispatch() 比较合适。添加日志功能,把 Action 和 State 打印出来,可以对store.dispatch进行如下改造。

let next = store.dispatch;
store.dispatch = function dispatchAndLog(action) {
  console.log('dispatching', action);
  next(action);
  console.log('next state', store.getState());
}

总结:中间件其实就是一个函数,对store.dispatch方法进行了改造,在发出 Action 和执行 Reducer 这两步之间,添加了其他功能。

中间件的用法:

const store = createStore(
  reducer,
  initial_state,
  applyMiddleware(logger)
);

createStore方法可以接受整个应用的初始状态作为参数,将中间件(logger)放在applyMiddleware方法之中,传入createStore方法,就完成了store.dispatch()的功能增强。

applyMiddleware 是Redux 的原生方法,作用是将所有中间件组成一个数组,依次执行。下面是它的源码:

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    var store = createStore(reducer, preloadedState, enhancer);
    var dispatch = store.dispatch;
    var chain = [];

    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    };
    chain = middlewares.map(middleware => middleware(middlewareAPI));
    dispatch = compose(...chain)(store.dispatch);

    return {...store, dispatch}
  }
}

可以看到,中间件内部(middlewareAPI)可以拿到getState和dispatch这两个方法。

异步操作的一个解决方案,就是让 Action Creator 返回一个 Promise 对象。看一下redux-promise的源码:

export default function promiseMiddleware({ dispatch }) {
  return next => action => {
    if (!isFSA(action)) {
      return isPromise(action)
        ? action.then(dispatch)
        : next(action);
    }

    return isPromise(action.payload)
      ? action.payload.then(
          result => dispatch({ ...action, payload: result }),
          error => {
            dispatch({ ...action, payload: error, error: true });
            return Promise.reject(error);
          }
        )
      : next(action);
  };
}

从上面代码可以看出,如果 Action 本身是一个 Promise,它 resolve 以后的值应该是一个 Action 对象,会被dispatch方法送出(action.then(dispatch)),但 reject 以后不会有任何动作;如果 Action 对象的payload属性是一个 Promise 对象,那么无论 resolve 和 reject,dispatch方法都会发出 Action。

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

如何将范式化数据后再次转化

什么?好不容易转化成自己想要的数据结构,还需要再次转化吗?很遗憾告诉你,是的。因为范式化后的数据只便于我们在维护Redux,而界面业务渲染的数据结构往往跟我们处理后的数据是不一样的,举个栗子:bootstrap或者ant.design的表格渲染数据结构是这个样的:

const dataSource = [{ key: '1', name: '胡彦斌', age: 32, address: '西湖区湖底公园1号' }, { key: '2', name: '胡彦祖', age: 42, address: '西湖区湖底公园1号' }];

1
2
3
4
5
6
7
8
9
10
11
const dataSource = [{
  key: '1',
  name: '胡彦斌',
  age: 32,
  address: '西湖区湖底公园1号'
}, {
  key: '2',
  name: '胡彦祖',
  age: 42,
  address: '西湖区湖底公园1号'
}];

因而在界面引用State上的数据时,我们需要一个中介,把范式化的数据再次转化成业务数据结构。我相信这个步骤十分简单,只需要写一个简单的转换器就行了:

const transform = (source) => { const data = source.entities.bykey; return Object.keys(data).map(v => data[v]); }; const mapStateToProps = (state,ownProps) => ({table: transform(state)}) export default connect(mapStateToProps)(view)

1
2
3
4
5
6
7
8
const transform = (source) => {
    const data = source.entities.bykey;
    return Object.keys(data).map(v => data[v]);
};
 
const mapStateToProps = (state,ownProps) => ({table: transform(state)})
 
export default connect(mapStateToProps)(view)

如果你在mapStateToProps里面断点调试,你会发现每一次dispatch都会强行执行mapStateProps方法保证对象的最新状态(除非你引用的是基础类型数据),因此,不管界面的操作是如何,被connect数据都会被强行执行一次,虽然界面没有变化,但是显然,js的性能会有折扣,尤其是对深度对象的复杂处理。因此,官方推荐我们创建可记忆的函数高效计算Redux Store里面的衍生数据。

3.Redux中的connect()方法

2.基本概念和API

  Redux 的核心就是 store, action, reducer   store.dispatch(action) ——> reducer(state, action) ——> final state

(1)store 就是保存数据的地方,redux 提供createStore 函数,生成Store

    store = redux.createStore(reducer, []);

        store.getState() //返回store的当前状态

  Store 允许使用store.subscribe方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。

  store.subscribe(listener);

  store.subscribe 方法返回一个函数,调用这个函数就可以解除监听

  let unsubscribe = store.subscribe(() =>

    console.log(store.getState())

  );

  unsubscribe(); //解除监听

  Store 的实现

store.getState() //获取当前状态

store.dispatch() //触发action

store.subscribe() //监听state状态

import { createStore } from ‘redux’;

let { subscribe, dispatch, getState } = createStore(reducer, window.STATE_FORM_SERVER);

window.STATE_FORM_SERVER //是整个应用的初始状态值

 (2)action 是一个普通的object,必须有一个type属性,表明行为的类型。

  const action = {

    type: ’add_todo’,

    text: ‘read’,

    time

    …

  }

  action描述当前发生的事情,改变State的唯一方法就是通过action,会将数据送到store。

  一般用actionCreator 工厂模式产生,View要发出的消息类型对应action的类型,手写起来很费劲。

  const ADD_TODO = “添加 todo”;

  function addTodo(text){

    return {

           type: ADD_TODO,

      text 

          }

  }

  const action = addTodo(‘Learn’);

  addTodo 方法就是一个Action Creator

  View 发出Action的唯一途径 store.dispatch(action) //触发事件 

 (3)reducer 其实就是一个普通函数,主要用来改变state. Store 收到View 发出的Action 以后,必须返回一个新的State,View 才会发生变化。 而这个计算新的State的过程就叫Reducer.

  const reducer = function(state, action){

  switch(state.text){

             case ‘add_todo’:

  return state.contact(‘…’);

              default:

  return state;

         }

  }

   当然实际开发不像上面例子这么简单,需要在创建state的时候就知道state的计算规则,将reducer传入:

  store = redux.createStore(reducer);

  Reducer 纯函数,只要有同样的输入必然返回同样的输出。不能改变原来的state而是通过Reducer返回一个新的state。

//state 是一个对象

function reducer(state, action){

return Object.assign({},state, {thingToChange});

         return {…state, …newState};

}

//state 是一个数组

function reducer(state, action){

return […state, newItem];

}

理解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到子代组件们.

结束语

React+Redux组合在实际应用过程中需要优化的地方还很多,这里只是简单展示其中的一个小点。虽然在计算dom界面变化时React已经做得足够好,但并不意味着我们可以不用为界面渲染问题背锅,React肩负了多数界面更新计算的任务,而让前端开发人员更多地去处理数据,因此,我们可以在这里层多花点时间把项目做好。

           units: {

5.React+Redux

redux将所有组件分为UI组件和容器组件。

UI 组件有以下几个特征。

  1. 只负责 UI 的呈现,不带有任何业务逻辑
  2. 没有状态(即不使用this.state这个变量)
  3. 所有数据都由参数(this.props)提供
  4. 不使用任何 Redux 的 API

UI 组件又称为"纯组件",即它纯函数一样,纯粹由参数决定它的值。

 

容器组件的特征恰恰相反。

  1.负责管理数据和业务逻辑,不负责 UI 的呈现

  2.带有内部状态

  3.使用 Redux 的 API

React-Redux 提供connect方法,用于从 UI 组件生成容器组件。connect的意思,就是将这两种组件连起来。

import { connect } from 'react-redux'

const VisibleTodoList = connect(

  mapStateToProps,

  mapDispatchToProps

)(TodoList)

上面代码中,connect方法接受两个参数:mapStateToProps和mapDispatchToProps。

它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将state映射到 UI 组件的参数(props),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。

 


normalizr方法使用指南

官方最荐normalizr模块,它的用法还是需要时间的去研究的。下面我们就以上面的数据为示例,说明它的用法:

$ npm i normalizr -S //下载模块 ......... import {normalize, schema} from 'normalizr';//日常导入,没问题 //原始数据 const TablesSource = { query: 'tables', tableId: 10, data: [{ key: 11, name: '胡彦斌', age: 32, address: '西湖区湖底公园1号' }, { key: 12, name: '胡彦祖', age: 42, address: '西湖区湖底公园1号' }] }; //创建实体,名称为bykey,我们看到它的第二个参数是undefined,说明它是最后一层级的对象 const bykey = new schema.Entity('bykey', undefined, { idAttribute: 'key' }); //创建实体,名字为table,索引是tableid。 const table = new schema.Entity('table', { data: [bykey] //这里需要说明这些实体的关系,意思是bykey原来table下面的是一个数组,他对应的是data数据,bykey将会取这里的数据建立一个以key为索引的对象。 }, { idAttribute: 'tableId'//以tableId为为索引 }); const normalizedData = normalize(TablesSource, table);//生成新数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
$ npm i normalizr -S //下载模块
 
.........
 
import {normalize, schema} from 'normalizr';//日常导入,没问题
//原始数据
const TablesSource = {
    query: 'tables',
    tableId: 10,
    data: [{
        key: 11,
        name: '胡彦斌',
        age: 32,
        address: '西湖区湖底公园1号'
    }, {
        key: 12,
        name: '胡彦祖',
        age: 42,
        address: '西湖区湖底公园1号'
    }]
};
 
//创建实体,名称为bykey,我们看到它的第二个参数是undefined,说明它是最后一层级的对象
const bykey = new schema.Entity('bykey', undefined, {
    idAttribute: 'key'
});
//创建实体,名字为table,索引是tableid。
const table = new schema.Entity('table', {
    data: [bykey] //这里需要说明这些实体的关系,意思是bykey原来table下面的是一个数组,他对应的是data数据,bykey将会取这里的数据建立一个以key为索引的对象。
}, {
    idAttribute: 'tableId'//以tableId为为索引
});
 
const normalizedData = normalize(TablesSource, table);//生成新数据结构

说明:new schema.Entity的第一个参数表示你创建的最外层的实体名称,第二个参数是它和其他新创建的实体的关系,默认是最小的层级,即它只是最后一层,不包含其他层级了。第三个参数里面有个idAttribute,指的是以哪个字段为索引,默认是”id”,它也可以是个参数,返回你自己构造的唯一值,记住,是唯一值。按照这样的套路,你可以随意构建你想要的扁平化数据结构,无论源数据的层级有多深。我们最终都会得到希望的数据结构。

{ "entities": { "bykey": {、、实体名称 "11": {//我们之前设置的唯一所用key "key": 11, "name": "胡彦斌", "age": 32, "address": "西湖区湖底公园1号" }, "12": { "key": 12, "name": "胡彦祖", "age": 42, "address": "西湖区湖底公园1号" } }, "table": {//实体名 "10": {、、唯一所用tableid "query": "tables", "tableId": 10, "data": [ //data变成了储存key值索引的集合了!因为在之前我们说明了两个实体之间的关系 data: [bykey] 11, 12 ] } } }, "result": 10//这里同样储存着table实体里面的索引集合 normalizr(TableSource, table) }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
  "entities": {
    "bykey": {、、实体名称
      "11": {//我们之前设置的唯一所用key
        "key": 11,
        "name": "胡彦斌",
        "age": 32,
        "address": "西湖区湖底公园1号"
      },
      "12": {
        "key": 12,
        "name": "胡彦祖",
        "age": 42,
        "address": "西湖区湖底公园1号"
      }
    },
    "table": {//实体名
      "10": {、、唯一所用tableid
        "query": "tables",
        "tableId": 10,
        "data": [ //data变成了储存key值索引的集合了!因为在之前我们说明了两个实体之间的关系 data: [bykey]
          11,
          12
        ]
      }
    }
  },
  "result": 10//这里同样储存着table实体里面的索引集合 normalizr(TableSource, table)
}

github上有详细的官方文档可供查找,卤煮在此只是简单的说明一下用法,诸位可以仔细观察文档上的用法,不需要多少时间就可以熟练掌握。到此为止,万里长城,终于走完了第一步。

export const sub = () => ({

创建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中函数是传引用的,所以函数名可以作为函数的引用,通过另一函数的参数来传递. 厉害 
			  

本文由金沙棋牌发布于金沙棋牌官方平台,转载请注明出处:学习总结

关键词:

上一篇:没有了

下一篇:没有了