/** * @file Container组件 * @author dongtiancheng */ import * as React from 'react'; import { isEmpty, isObjectLike, clone, isNil, isObject, find, each, isPlainObject } from 'lodash'; import { BasicContainer, ContainerProps, getRuntimeContext } from './types'; import { connect } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; import { actionCreators, IContainerAction } from './action'; import { RootState } from '../../data/reducers'; import { DataProvider } from '../DataProvider/Controller'; import { ContainerPropsInterface } from '../../../components/Container/Container'; import { compileExpressionString, isExpression, parseExpressionString } from '../../util/vm'; import { DataCustomer } from '../DataCustomer/Controller'; import { createChild } from '../../util/createChild'; // First Init Life Circle: // ComponentWillMount -> Render -> ComponentDidMount // Component Update Life Circle: // componentWillReceiveProps -> shouldComponentUpdate -> ComponentWillUpdate -> Render -> ComponentDidMount export class Container extends BasicContainer { static WrappedComponent: string; static displayName: string; private dataProvider: DataProvider; private dataCustomer: DataCustomer; constructor(props: ContainerProps) { super(props); this.dataProvider = new DataProvider(); this.dataCustomer = new DataCustomer(); } async componentWillMount() { let info = this.getPropsInfo(this.props.info, this.props, ['children', 'data', 'dataCustomer', 'dataProvider']); if (info.model) { if (isEmpty(info.data)) { info.data = { $loading: false }; } const defaultCustomer = { mode: 'pass', name: '$SELF_PASS_CUSTOMER', config: { model: this.props.info.model, assign: {} } }; if (info.dataCustomer) { info.dataCustomer.customers.unshift(defaultCustomer); } else { info.dataCustomer = { customers: [defaultCustomer], groups: [] }; } this.dataCustomer.initCustomerConfig(info.dataCustomer); // to keep it safe, this.props.info should be readonly Object.freeze(this.props.info); const providerActions = { setDataList: this.props.setDataList, asyncLoadDataProgress: this.props.asyncLoadDataProgress, asyncLoadDataSuccess: this.props.asyncLoadDataSuccess, asyncLoadDataFail: this.props.asyncLoadDataFail, syncLoadDataSuccess: this.props.syncLoadDataSuccess, syncLoadDataFail: this.props.syncLoadDataFail }; // 用于初始化的内置Provider const initProvider = { mode: 'init', config: info.data, __previousConfig: null }; this.dataProvider.requestForData([initProvider], providerActions, info, this.props, this.context); } else { console.error('Container Component Should have model property'); } } componentWillReceiveProps(nextProps: ContainerProps) { let info = this.getPropsInfo(this.props.info, this.props, ['children', 'data', 'dataCustomer', 'dataProvider']); const providerActions = { setDataList: this.props.setDataList, asyncLoadDataProgress: this.props.asyncLoadDataProgress, asyncLoadDataSuccess: this.props.asyncLoadDataSuccess, asyncLoadDataFail: this.props.asyncLoadDataFail, syncLoadDataSuccess: this.props.syncLoadDataSuccess, syncLoadDataFail: this.props.syncLoadDataFail }; let dataProvider = this.props.info.dataProvider; if (dataProvider) { this.dataProvider.requestForData(dataProvider, providerActions, info, nextProps, this.context); } } componentWillUnmount() { let info = this.getPropsInfo(this.props.info, this.props, ['children', 'data', 'dataCustomer', 'dataProvider']); if (info.model) { this.props.clearData({ model: info.model }); } } // shouldComponentUpdate(nextProps: ContainerProps) { // // 判断Container数据是否有更新. // return isEqual(this.props.$data, nextProps.$data); // } render() { let info = this.getPropsInfo(this.props.info, this.props, ['children', 'data', 'dataCustomer', 'dataProvider']); if (!info.model) { return
model should defined in container like components
; } if (!info.children) { return
children props must be specific in Container Component
; } const setData = (name: string, value: any) => { if (isObjectLike(value)) { value = clone(value); } if (info.bind && info.bind instanceof Array) { let bindTarget = find(info.bind, o => o.child === name); if (bindTarget && this.props.$setData) { this.props.$setData(bindTarget.parent, value); } } this.props.setData({ name: name, value: value }, info.model); }; const setMultiData = (items: { name: string, value: any }[]) => { items = items.map(item => { if (isObjectLike(item.value)) { item.value = clone(item.value); } return item; }); if (info.bind && info.bind instanceof Array) { let bindItems = items .filter(i => { let bindTarget = find(info.bind, f => f.child === i.name); if (bindTarget) { i.name = bindTarget.parent; } return !bindTarget; }); if (bindItems.length > 0 && this.props.$setMultiData) { this.props.$setMultiData(bindItems); } } this.props.setMultiData(items, info.model); }; const $deleteData = (name: string) => { if (info.bind && info.bind instanceof Array) { let bindTarget = find(info.bind, o => o.child === name); if (bindTarget && this.props.$deleteData) { this.props.$deleteData(bindTarget.parent); } } this.props.deleteData({name}, info.model); }; // Container Component no long compile expression string for child // instead, AbstractComponent should compile it by themSelf let childElements = info.children.map((child, index) => { child = this.getPropsInfo(child, this.props, [], false, ['show', 'hidden']); let childElement = createChild(child, { ...this.props, info: child, model: info.model, $data: this.props.$data, $parent: this.props.$parent, options: this.props.options, dataCustomer: this.dataCustomer, $index: this.props.$index, $item: this.props.$item, $setData: setData, $deleteData: $deleteData, $setMultiData: setMultiData, key: `${child.type}_${index}` }); return this.renderChildren(child, childElement); }); const containerStyle = { ...info.style, width: '100%' }; return (
{childElements}
); } } const mapStateToProps = (state: RootState, ownProps: ContainerPropsInterface) => { let runTime = getRuntimeContext(ownProps, {}); // direct compile let info = compileExpressionString(ownProps.info, runTime, ['children', 'data', 'dataCustomer', 'dataProvider'], false); return { $data: state.container[info.model] || {} }; }; const mapDispatchToProps = (dispatch: Dispatch) => bindActionCreators({ setData: actionCreators.setData, setDataList: actionCreators.setDataList, setMultiData: actionCreators.setMultiData, clearData: actionCreators.clearData, deleteData: actionCreators.deleteData, asyncLoadDataProgress: actionCreators.asyncLoadDataProgress, asyncLoadDataSuccess: actionCreators.asyncLoadDataSuccess, asyncLoadDataFail: actionCreators.asyncLoadDataFail, syncLoadDataSuccess: actionCreators.syncLoadDataSuccess, syncLoadDataFail: actionCreators.syncLoadDataFail }, dispatch); /** * 执行container组件嵌套的逻辑 * * @param stateProps 当前container组件的mapStateToProps返回的对象 * @param dispatchProps 当前container组件的mapDispatchToProps返回的对象 * @param parentProps connect组件接收到的props * @return 当前container组件的props */ export const mergeProps = (stateProps: { $data: Object }, dispatchProps: ContainerProps, ownProps: ContainerPropsInterface): ContainerProps => { // 启用兼容模式, 父级优先 if (ownProps.options && ownProps.options.oldNestContainerCompatible) { let parentProps = ownProps.$data || {}; let stateData = stateProps.$data; if (isObject(ownProps.info.parentMapping)) { let runTime = getRuntimeContext(ownProps, {}); let parentMappingRet = compileExpressionString(ownProps.info.parentMapping, { ...runTime, $parent: parentProps, $data: stateProps.$data }); if (parentMappingRet) { let originalData = stateProps.$data; Object.assign(stateData, originalData); } } else { // 屏蔽$loading属性 delete parentProps['$loading']; Object.assign(stateData, parentProps); } return Object.assign({}, ownProps, stateProps, dispatchProps, { $data: stateData }); } // 子级优先 let parentData = ownProps.$data; if (ownProps.$parent && parentData) { parentData['$parent'] = ownProps.$parent; } if (parentData && !isEmpty(parentData) && ownProps.info.props) { let runTime = getRuntimeContext(ownProps, {}); each(ownProps.info.props, (val: any, name) => { // priority == parent if (typeof val === 'string' && isExpression(val)) { let result = parseExpressionString(val, { ...runTime, $parent: parentData }); if (isNil(result)) { return; } stateProps.$data[name] = result; return; } if (isPlainObject(val)) { let prop = val.prop; let priority = val.priority; // priority === child and nothing if (typeof prop !== 'string' || !isExpression(prop)) { return; } let result = parseExpressionString(prop, { ...runTime, $parent: parentData }); if (isNil(result)) { return; } // 字级优先,而字级又没有值的时候,就给个初始值 if (priority === 'child') { if (!(name in stateProps.$data)) { stateProps.$data[name] = result; } return; } stateProps.$data[name] = result; } }); } return Object.assign({}, ownProps, stateProps, dispatchProps, { $parent: parentData }); }; export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(Container);