美文网首页
React项目模板-app.jsx源码解析

React项目模板-app.jsx源码解析

作者: stevekeol | 来源:发表于2019-05-07 11:06 被阅读0次

app.jsx源码如下:

import React from 'react';
import { render } from 'react-dom';
import { bindActionCreators, createStore, applyMiddleware } from 'redux';
import { connect, Provider } from 'react-redux';
import thunkMiddleware from 'redux-thunk';

import Deskmark from 'components/Deskmark'; //项目的容器组件
import rootReducer from 'reducers';
import * as actionCreators from 'actions';

import 'bootstrap/scss/bootstrap.scss';

// create store with middlewares
const store = applyMiddleware(
  thunkMiddleware
)(createStore)(rootReducer);

// create root component based on component Deskmark
const App = connect(
  state => ({ state }),
  dispatch => ({
    actions: bindActionCreators(actionCreators, dispatch),
  })
)(Deskmark);

// create DOM container
const container = document.body.appendChild(
  document.createElement('div')
);

// render root conponent with store to DOM container
render(
  <Provider store={store}>
    <App />
  </Provider>,
  container
);

需要理顺以下问题:

1. bindActionCreator的作用和工作原理;
2. createStore的作用和工作原理;
3. applyMiddleware的作用和工作原理;
4. Provider的作用和工作原理;
5. connect的作用和工作原理;
6. thunkMiddleware的作用和工作原理;

Flux理念(大致了解即可)

为什么要推出Flux理念:
(1)React Component的初衷是提供界面级别的组件方案,因此将业务逻辑写进React Component。但是随着应用逻辑变得复杂,Component内部的业务逻辑随之膨胀,会损失复用性和可维护性。
(2)因此,需要将业务逻辑从视图组件中抽出来,分为action的构造行为(action creator)和响应action对数据进行更新的行为(reducer),这样数据、逻辑和展现各自分立,清晰明了。

核心概念:单项数据流
Flux的整个流程:
(1) Action:  即一个描述行为的json对象;
(2) Dispatcher:是一个信息的分发中心,也是Action和Store的连接中心。
                Dispatcher可以使用dispatch方法执行一个Action,
                并且可以用register方法注册回调,回调中处理Store的内容。
(3) Store: Store处理完后,它可以使用emit方法向其它地方发送命名为'change'的广播,通知它们Store已经变更.
(4) View: View层监听change事件,一旦change事件被触发,View层调用setState来更新整个UI。

Redux概要

Redux是官方提供的一种基于Flux理念的一种实现,是一种状态容器,提供了可预测的状态管理。
1. 为什么需要状态管理?
状态起始就是应该用运行时需要的各种各样的动态数据,诸如:服务器返回的数据,本地生成还没有持久化到服务器的数据,本地缓存数据,服务器数据加载状态,当前路由等.
2. 状态管理容器那么多,为何选用Redux?
React是一个由状态整体输出界面整体的View层实现,而Redux着眼于对状态整体的维护,不会产生出具体变动的部分。因此React搭配Redux很适合.
3. Redux是如何让应用状态(state)可预测的?
Redux的三大定律:
'单一数据源': 即整个应用的state存储在一个称之为store的js对象中;
'state只读': 即只能通过触发action来改变state, 而不能直接在state上面修改数据;
'使用纯函数执行修改': reducer是纯函数,接受先前的state和待处理的action,返回新的state.
4. store和state的区别是什么?(自己老是易混淆)
state是用于整个应用数据保存,是一个实实在在的数据对象;store是一个综合体,里面既有state,也有访问state的值的方法(store.getState()),也能通过dispatch()执行一个action,也可通过subscrible(listener)注册回调,监听state的变化。

Redux的三大组成

1. action: 是一个描述行为的json对象,利用store的dispatch方法,可以被传递到store中(可看出,action是store的唯一信息来源).

//原始写法
const CREATE_POST = 'CREATE_POST';
let createPostAction = {
  type: CREATE_POST,
  data: {id: 1, title: 'testTitle'}
}

//改进写法
function createPost(data) {
  return {
    type: CREATE_POST,
    data: data
  }
}
写成actionCreator的意义在于:Redux中,actionCreator返回一个action对象(并非像Flux理念中直接调用dispatch方法),这异步应用中,可以条件执行或者延迟执行。??

2. reducer:定义整个程序的state如何响应(接受先前的state和待处理的action,返回新的state)

function posts(state=initalPostState, action) {
  switch(action.type) {
    case CREATE_POST: 
      return [...state, action.data];
    case DELETE_POST: 
      return state.filter((post) => {
        return post.id != action.id;
      });
    default: 
      return state;
  }
}

function user(state=initailState, action) {
  switch (action.type) {
    case USER_LOGIN:
      return Object.assign({}, state, {
        isLogin: true,
        userData: action.data
      });
    default:
      return state;
  }
}

为了方便多人协作、文件结构清晰、代码逻辑合理,上面两个reducer建议分文件写。但是整个应用的state数据都存储在一个对象中,因此需要将两个/多个reduer合并:
import { combineReducers } from 'redux';
const rootReducer = combineReducers({
  posts,
  user
});

3. store: store是action和reducer的黏合剂(连接中心),介于二者之间。
(1)保存整个程序的state;
(2)可通过getSate()访问state的值;
(3)可通过dispatch()执行一个action;
(4)可通过subscribe(listener)注册回调,监听state的变化。

现在实现Redux的store:
import { createStore } from 'redux';
let store = createStore(rootReducer);
即将rootReducer传给redux提供的store创建函数:createStore。因为store要将action用dispatch方法传给reducer。

现在可以使用store:
(1).  console.log(store.getState()); //打印store的初始状态
(2).  store.subscrible( () => {
            console.log(store.getState());
        }); //监听state的变化,每次变化后都打印出新的state
(3).  store.dispatch( createPost({id: 1, title: 'titleTest'});
        //即将该actionCreator创建的action发送给reducer。

Redux的中间件:middleware

1. middleware触发时机:在action被dispatch分发(触发)之后,在调用最终reducer之前。
2. middleware可以接触到: action, store的getState和dispatch方法(原因:)
3. middleware的工作内容:
(1)在原有的action基础上创建一个新的action和dispatch(action转换,用于可以不action处理等),
(2)也可出发一些额外的行为(如日志记录),
(3)最后middleware可通过next触发后续的middleware和reducer本身的执行。
【反思】:
(1)为什么触发时机要放在action被dispatch触发的时候(注意,不是之后,而是当时!需要揣摩,目前已经搞懂),调用最终reducer之前?
答: 因为reducer的作用是接受原有的state和待处理的action,返回新的state。
一旦有了新的state, 即store有了变更,render便会自动更新相关UI。
即一旦调用reducer,后续没有时机再执行操作。
而action是用户的行为描述,中间件肯定在这之后。
(2)dispatch哪儿来的?
答:当使用redux提供的createStore时,返回的store即带有dispatch方法。
该dispatch方法会在store内部将action交给reudcer处理。
其中,reducer都是传进createStore(store的创建函数)中来的,action是业务代码中手动传给store.dispatch()的(初级,后文需要升级)。
(3)action具体是什么时候传给createStore()的?
//源码(初级,后文需要升级)
import { createStore, combineReducers, applyMiddleware } from 'redux';
let rootReducers = combineReducers({
    reducer1,
    reducer2
});
let store = createStore(rootReducers, applyMiddleware(logger, creashReporter));
store.dispatch(addTodo('test first'));  //现在的store.dispatch()已经有了两个中间件的作用。
【重要!】源码1中的action是手动传给store.dispatch()的(后文会升级);

Redux的applyMiddleware

既然上文提到,middleware的功能是作用于store.dispatch,返回一个增强型的store.dispatch。那么需要一个方法将middleware和store.dispatch结合起来,我们称之为applyMiddleware。

//旧版风格
const createStoreWithMiddleware = applyMiddleware(thunkMiddleware)(createStore);
const store = createStoreWithMiddleware(rootReducers);

//新版风格
const store = applyMiddleware(
  thunkMiddleware
)(createStore)(rootReducer);

解释:
因为 middleware 最终是用来增强 store.dispatch的, 
因此 applyMiddleware(thunkMiddleware)作为一个整体,
需要接受createStore作为参数,返回一个增强型的creatStore(提供增强型的 store.dispatch);

中间件 thunkMiddleware的作用

中间件的作用是返回一个增强型的dispatch。
thunkMiddleware中间件的存在,除了可以dispatch一个普通的action对象,还可以dispatch一个函数(即thunk)。
在该函数中,可以完成一些逻辑,诸如:延迟dispatch、条件性dispatch、在该thunk中多次dispatch等等。

中间件 promiseMiddleware的作用

当有异步的action(即action是一个函数,即是一个上文提到的thunk),有一种做法是定义三个action type(如FETCH_LIST_PENDING、FETCH_LIST_FULLFILLED、FETCH_LIST_REJECTED)。然后在发起行为,成功和失败时,分别dispatch这3中type的action。

自动化完成上述行为,具体如何自动化,见下面源码:

function fetchEntryList() {
    return {
        type: FETCH_LIST, 
        payload: storage.getall()
    };
}
1. 只需要像创建普通action一样创建异步action,不同的是这里action的preload字段的值是一个promise。
2. 当发现这一点,promiseMiddleware中间件会在当时,以及promise被resolve/reject时,分别创建FETCH_LIST_PENDING、FETCH_LIST_FULLFILLED、FETCH_LIST_REJECTED的action。
3. (注意到,这是对thunkMiddleware的升级:thunkMiddleware允许actionCreator返回一个函数,再将该函数传给dispatch;promiseMiddleware还是返回一个普通js对象,但是除了type字段,还有一个字段preload,该字段的值是一个promise对象。)
4. 只需要再reducer中响应promiseMiddleware创建的action即可:

//reducers/entries/list.js
import * as ActionTypes from 'actions';
const initialState = {
  isFetching: false,
  data: [],
};
const { pendingOf, fulfilledOf } = ActionTypes;
export default function (state = initialState, action) {
  const { type, payload } = action;
  switch (type) {
    case pendingOf(ActionTypes.FETCH_ENTRY_LIST):
      return {
        ...state,
        isFetching: true,
      };
    case fulfilledOf(ActionTypes.FETCH_ENTRY_LIST):
      return {
        ...state,
        isFetching: false,
        data: payload,
      };
    default:
      return state;
  }
}
由上可见,只要异步操作结果可以使用promise表示时redux-promise-middleware(代码中要写:promiseMiddleware)都可以大大简化actionCreator实现。

Redux的Provider和connect

【作用!!!】增强原有的组件树,使原有组件树的所有节点都拥有接触到store的能力:能够监听、读取状态数据并触发action。
// create root component based on component Deskmark
const App = connect(
  state => ({ state }),
  dispatch => ({
    actions: bindActionCreators(actionCreators, dispatch),
  })
)(Deskmark);

// create DOM container
const container = document.body.appendChild(
  document.createElement('div')
);

// render root conponent with store to DOM container
render(
  <Provider store={store}>
    <App />
  </Provider>,
  container
);

react-redux:Redux官方提供的React绑定,用于在react项目中使用Redux。
react-redux提供了两个api:Provider和connect。

'Provider' : store的提供者,一般情况下,将原有组件的根节点包裹在Provider中,
              这样整个组件树上的节点都可以通过connect获取store;
'connect':  用来'连接'store和组件。

connect的用法:

const App = connect(mapStateToProps, mapDispatchToProps)(counter);
1. counter称之为展示组件(即原有组件树的根节点)。
2. mapStateToProps(生成数据属性)就是从全局的状态数据中挑选、计算得到展示组件所需要数据的过程,即从state到组件属性的映射。
3. mapDispatchToProps(生成行为属性)接受参数(store.dispatch)返回一个普通的js对象,对象的内容也会被合并到最终的展示组件上。
//bindActionCreators的作用
  dispatch => ({
    actions: bindActionCreators(actionCreators, dispatch)
  })
// connect()的第二个参数,是生成行为属性,该参数,接受actionCreators和dispatch,对每一个actionCreator绑定dispatch(使得一旦有了action,随机被dispatch出去),返回一个普通的js对象(即一系列的actions)。

组件的数据属性和行为属性的来源:

需要弄懂以下问题:

1. 当我们说如何使用redux的时候,说的其实是如何获取并使用store的内容(状态数据),以及创建并触发action的过程;
2. Redux中store的实现更多表现为reducer,通过reducer创建store的代码往往很少,且不包含业务逻辑;所以,讨论设计store的时候,讨论的其实是实现reducer。‘

this.props.actions.XX中this.props什么时候传入的?为什么用props?
【答】:因为用了Provider和connect,所以所有组件上均可以直接使用this.props.state或者this.props.actions。

使用形式有:
(1)生命周期函数中:
     componentDidMount(){
        this.props.actions.fetchEntryList();
     }
(2)render()中:
render() {
  const {state, actions} = this.props;
  const {isEditing, selectedId} = state.editor;
  const items = state.items;

  <ItemEditor 
     onSave = {actions.saveEntry}
  />
}

store不是用createStore创建的么,为什么还需要在reducer中实现store?
【答】:rootReducers是将多个reducer组装在一起,使得各个单独的store形成一个全局唯一的整体。createStore(rootReducers)可看出创建store实际上就是利用了rootReducers。因此讨论设计store的时候,讨论的往往就是reducer的实现。
【记住】:行为属性和数据属性是分开的!意思就是不要指望actions的那些onSave()之类的方法也在state中,其实不是的,这些是在actions中,actions和state是分开的。state中全是数据属性。(原先的理解,应该更正为:actions和state都在store中!而非actions在state中,因为我把state和store一开始没搞清楚,弄混淆了)

创建异步action的再次解读

*** 第一份源码 ***

//actions/index.js
export const pendingOf = actionType => `${actionType}_PENDING`;
export const fulfilledOf = actionType => `${actionType}_FULFILLED`;
export const rejectedOf = actionType => `${actionType}_REJECTED`;

export const FETCH_ENTRY_LIST = 'FETCH_ENTRY_LIST';

export function fetchEntryList() {
  return {
    type: FETCH_ENTRY_LIST,
    preload: storage.getAll()
  };
}
解释:
1. pendingOf等三个函数为何放在actions中:这三个函数是用来生成最后需要dispatch给reducer 的action的type的。因此放在actions/index.js中符合边界清晰的原则。
2. 因为有了promiseMiddleware等中间件,dispatch的时候,type是带有后缀的type,preload是异步操作已经有了结果之后的返回值。(即:dispatch给reducer时候,type是FETCH_ENTRY_LIST__FULFILLED的样式;preload是具体的data/error)。

//reducers/items.js
import * as ActionTypes from '../actions';

const initialState = {
  isFetching: false,
  items: [],
};

const { pendingOf, fulfilledOf } = ActionTypes;

export default function (state = initialState, action) {
  console.log('action:');
  console.log(action);
  const { type, payload } = action;
  switch (type) {
    case pendingOf(ActionTypes.FETCH_ENTRY_LIST):
      return {
        ...state,
        isFetching: true,
      };
    case fulfilledOf(ActionTypes.FETCH_ENTRY_LIST):
      return {
        ...state,
        isFetching: false,
        items: payload,
      };
    default:
      return state;
  }
}

//components/Deskmark/index.jsx
    ...
    const { state, actions } = this.props;
    const { items } = state.items;
    ...
【解释】:
1. reducer的作用就是拿着当前reducer内部有的initialState,和刚进来的action(type,preload),来决定如何更新当前reducer内部的state(是一个json对象)。

actionCreator中preload的用法:

actionCreator返回一个js对象。该action,必须包含一个type字段,鼓励将负载信息放在preload字段里面。

//第一种:
返回的是js对象,preload上可以挂载一个一次性异步操作。
export function fetchEntryList() {
  return {
    type: FETCH_ENTRY_LIST,
    payload: storage.getAll(),
  };
}

//第二种:
返回的是js对象,preload上可以挂载一个一次性异步操作。
export function fetchEntry(id) {
  return {
    type: FETCH_ENTRY,
    payload: {
      promise: storage.getEntry(id),
      data: id,
    },
  };
}

//第三种:
返回的不是一个js对象,而是一个函数的时候,thunkMiddleware中间件会将dispatch和getState传给该函数。在该函数中决定何时dispatch一个js对象。
export function deleteEntry(id) {
  const promise = storage.deleteEntry(id).then(() => id);
  return dispatch => {
    dispatch({
      type: DELETE_ENTRY,
      payload: {
        promise,
        data: id,
      },
    });
    promise.then(
      () => dispatch(fetchEntryList())
    );
  };
}

1. 当promiseMiddleware中间件检测到preload是一个promise的时候,会自动构造带有_FULLFILLED等后缀的type,并在preload中携带异步操作完成的结果。然后传递给reducer。
2. 

Promise的小注意

export function deleteEntry(id) {
  return getAll()
    .then(
      results => results.filter(
        result => result.id !== id
      )
    )
    .then(saveAll); 

// 当使用了saveAll()则只要删除一个item,所有的item都被删除了。
//(解释: saveAll()立即执行,即也在getAll()这个异步操作之前,
// 但是saveAll()没有传递任何参数值进来,因此相当于强行给STORAGE_KEY赋了空值。
// 此处其实暗藏着,前一个then传递回来的results作为参数给了saveAll)
}
preload的用法

相关文章

网友评论

      本文标题:React项目模板-app.jsx源码解析

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