React Native之RNRF框架源码解析

说是源码解析,但是目前对JS语法还不算熟,只能算是分析下,因为官方的文档实在是少的可伶

import Reducer, { findElement } from './Reducer';
.
.
.
       const initialState = getInitialState(scenesMap);
    const reducerCreator = props.createReducer || Reducer;

    const routerReducer = props.reducer || (
      reducerCreator({
        initialState,
        scenes: scenesMap,
      }));

    return routerReducer;

在RNRF中,有一个Reducer,如果createReducer属性未定义,则使用默认的Reducer(这是一个方法)

而我们目前写的createReducer是这样的:

const reducerCreate = params => {
    const defaultReducer = new Reducer(params);
    return (state, action) => {
        console.log('ACTION:', action);
        return defaultReducer(state, action);
    };
};

所以最终还是会触发RNRF中的Reducer方法

return (stateParam, actionParam) => {
    let state = stateParam;
    let action = actionParam;
    state = state || { ...initialState, scenes };
    assert(action, 'action should be defined');   //assert方法只是简单的判断第一个参数是否存在if(param1),不存在则抛出Error
    assert(action.type, 'action type should be defined');
    assert(state.scenes, 'state.scenes is missed');

    if (action.key) {
      if (ActionMap[action.type] === ActionConst.REFRESH) {
        let key = action.key;
        let child = findElement(state, key, action.type) || state.scenes[key];
        let sceneKey = child.sceneKey;
        if (child.base) {
          child = { ...state.scenes[child.base], ...child };
          assert(state.scenes[child.base], `No scene exists for base=${child.base}`);
          key = state.scenes[child.base].key;
          sceneKey = state.scenes[child.base].sceneKey;
        }
        assert(child, `missed child data for key=${key}`);
        // evaluate functions within actions to allow conditional set, like switch values
        const evaluated = {};
        Object.keys(action).forEach(el => {
          if (typeof action[el] === 'function' && typeof child[el] !== 'undefined'
            && typeof child[el] !== typeof action[el]) {
            evaluated[el] = action[el](child[el], child);
          }
        });
        action = { ...child, ...action, ...evaluated, sceneKey, key };

        // console.log("REFRESH ACTION:", action);
      } else {
        const scene = state.scenes[action.key];  //state.scenes 中包含所有的scene(包括子scene)
        assert(scene, `missed route data for key=${action.key}`);
        // clone scene  克隆当前的scene
        if (scene.clone) {
          action.parent = getCurrent(state).parent;
        }
      }
    } else {
      // set current route for pop action or refresh action
      if (ActionMap[action.type] === ActionConst.BACK_ACTION ||
          ActionMap[action.type] === ActionConst.BACK ||
          ActionMap[action.type] === ActionConst.POP_AND_REPLACE ||
          ActionMap[action.type] === ActionConst.REFRESH ||
          ActionMap[action.type] === ActionConst.POP_TO) {
        if (!action.key && !action.parent) {
          action = { ...getCurrent(state), ...action };
        }
      }

      // Find the parent and index of the future state
      if (ActionMap[action.type] === ActionConst.POP_TO) {
        /*
         * if a string is passed as only argument
         * Actions.filterParam will put it in the data property
         * otherwise look for the scene property
         */
        const target = action.data || action.scene;
        assert(target, 'PopTo() must be called with a single argument: ' +
        'either the scene name (string) or an object with within the scene property ' +
        'carrying the target scene to pop to');

        const targetEl = findElement(state, target, action.type);
        assert(targetEl, `Cannot find element name named ${target} within current state`);

        // target is a node
        let parent = targetEl.sceneKey;
        let targetIndex = 0;

        // target is child of a node
        if (!targetEl.children) {
          const targetParent = findElement(state, targetEl.parent, action.type);
          assert(targetParent, `Cannot find parent for target ${target}`);
          parent = targetParent.sceneKey;

          targetIndex = targetParent.children.indexOf(targetEl);
          assert(targetIndex > -1, `${target} does not belong to ${targetParent.sceneKey}`);
        }

        action.parent = parent;
        action.targetIndex = targetIndex;
      }

      // recursive pop parent
      if (ActionMap[action.type] === ActionConst.BACK_ACTION ||
          ActionMap[action.type] === ActionConst.BACK ||
          ActionMap[action.type] === ActionConst.POP_AND_REPLACE) {
        const parent = action.parent || state.scenes[action.key].parent;
        let el = findElement(state, parent, action.type);
        while (el.parent && (el.children.length <= 1 || el.tabs)) {
          el = findElement(state, el.parent, action.type);
          assert(el, `Cannot find element for parent=${el.parent} within current state`);
        }
        action.parent = el.sceneKey;
      }
    }

    switch (ActionMap[action.type]) {
      case ActionConst.BACK:
      case ActionConst.BACK_ACTION:
      case ActionConst.POP_AND_REPLACE:
      case ActionConst.POP_TO:
      case ActionConst.REFRESH:
      case ActionConst.PUSH:
      case ActionConst.PUSH_OR_POP:
      case ActionConst.JUMP:
      case ActionConst.REPLACE:
      case ActionConst.RESET:
        return update(state, action);

      default:
        return state;

    }
  };
export function getCurrent(state) {
  if (!state.children) {
    return state;
  }
  return getCurrent(state.children[state.index]);
}

function update(state, action) {
  // find parent in the state
  const props = { ...state.scenes[action.key], ...action };
  assert(props.parent, `No parent is defined for route=${action.key}`);
  return inject(state, action, props, state.scenes);
}
//这个就是reducer的核心方法了
function inject(state, action, props, scenes) { const condition = ActionMap[action.type] === ActionConst.REFRESH ? state.key === props.key || state.sceneKey === action.key : state.sceneKey === props.parent; // console.log("INJECT:", action.key, state.sceneKey, condition); if (!condition) { if (state.children) { const res = state.children.map(el => inject(el, action, props, scenes)); let changed = false; let changedIndex = -1; for (let i = 0; i < res.length; i++) { if (res[i] !== state.children[i]) { changed = true; changedIndex = i; break; } } return changed ? { ...state, children: res, index: changedIndex } : state; } return state; } let ind; switch (ActionMap[action.type]) { case ActionConst.POP_TO: { const targetIndex = action.targetIndex; return { ...state, index: targetIndex, children: refreshTopChild(state.children.slice(0, (targetIndex + 1)), action.refresh), }; } case ActionConst.BACK: case ActionConst.BACK_ACTION: { assert(!state.tabs, 'pop() operation cannot be run on tab bar (tabs=true)'); if (Platform.OS === 'android') { assert(state.index > 0, 'You are already in the root scene.'); } if (state.index === 0) { return state; } let popNum = 1; if (action.popNum) { assert(typeof(action.popNum) === 'number', 'The data is the number of scenes you want to pop, it must be Number'); popNum = action.popNum; assert(popNum % 1 === 0, 'The data is the number of scenes you want to pop, it must be integer.'); assert(popNum > 1, 'The data is the number of scenes you want to pop, it must be bigger than 1.'); assert(popNum <= state.index, 'The data is the number of scenes you want to pop, ' + "it must be smaller than scenes stack's length."); } return { ...state, index: state.index - popNum, from: state.children[state.children.length - popNum], children: refreshTopChild(state.children.slice(0, -1 * popNum), action.refresh), }; } // This action will pop the scene stack and then replace current scene in one go case ActionConst.POP_AND_REPLACE: { assert(!state.tabs, 'pop() operation cannot be run on tab bar (tabs=true)'); assert(state.index > 0, 'You are already in the root scene.'); let popNum = 1; if (action.popNum) { assert(typeof(action.popNum) === 'number', 'The data is the number of scenes you want to pop, it must be Number'); popNum = action.popNum; assert(popNum % 1 === 0, 'The data is the number of scenes you want to pop, it must be integer.'); assert(popNum > 1, 'The data is the number of scenes you want to pop, it must be bigger than 1.'); assert(popNum <= state.index, 'The data is the number of scenes you want to pop, ' + "it must be smaller than scenes stack's length."); } state = { ...state, index: state.index - popNum, from: state.children[state.children.length - popNum], children: state.children.slice(0, -1 * popNum), }; if (state.children[state.index].sceneKey === action.key) { return state; } const newAction = { duration: 0, // do not animate ...action, }; delete newAction.popNum; const newProps = { ...props }; delete newProps.popNum; state.children[state.children.length - 1] = getInitialState( newProps, scenes, state.index, newAction ); return { ...state, children: state.children }; } case ActionConst.REFRESH: return props.base ? { navBar: state.navBar, ...scenes.rootProps, ...props, key: state.key, from: null } : { ...state, ...props, key: state.key, from: null, }; case ActionConst.PUSH_OR_POP: ind = state.children.findIndex(el => el.sceneKey === action.key); if (ind !== -1) { return { ...state, index: ind, from: state.children[state.index], children: refreshTopChild(state.children.slice(0, ind + 1), action.refresh), }; } return { ...state, index: state.index + 1, from: null, children: [...state.children, getInitialState(props, scenes, state.index + 1, action)], }; case ActionConst.PUSH: if (state.children[state.index].sceneKey === action.key && !props.clone && checkPropertiesEqual(action, state.children[state.index])) { return state; } return { ...state, index: state.index + 1, from: null, children: [...state.children, getInitialState(props, scenes, state.index + 1, action)], }; case ActionConst.JUMP: { assert(state.tabs, `Parent=${state.key} is not tab bar, jump action is not valid`); ind = -1; state.children.forEach((c, i) => { if (c.sceneKey === action.key) { ind = i; } }); assert(ind !== -1, `Cannot find route with key=${action.key} for parent=${state.key}`); if (action.unmountScenes) { resetHistoryStack(state.children[ind]); } return { ...state, index: ind }; } case ActionConst.REPLACE: if (state.children[state.index].sceneKey === action.key) { return state; } state.children[state.children.length - 1] = getInitialState( props, scenes, state.index, action ); return { ...state, children: state.children }; case ActionConst.RESET: if (state.children[state.index].sceneKey === action.key) { return state; } state.children = state.children.splice(0, 1); state.children[0] = getInitialState(props, scenes, state.index, action); return { ...state, index: 0, from: null, children: state.children, }; default: return state; } }