说是源码解析,但是目前对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; } }