【问题标题】:wait for API call, then pass on initial value to component等待 API 调用,然后将初始值传递给组件
【发布时间】:2016-12-26 20:06:38
【问题描述】:

我在为依赖 API 调用的组件创建初始状态时遇到困难。

在我尝试实现它的每一种方式中,它都会进入一个循环,给我在渲染期间调用 this.setState() 的错误,这是一个反模式,...

问题: 如何根据子组件的状态为父组件实现初始状态?只有在reduxstate 中没有任何内容时才会触发

我可以显示代码,但它相当广泛,并且在多个文件之间划分。

编辑 1:根据要求 - 简化代码

父组件(过滤器)

class Filter extends Component {
    constructor(props) {
        super(props);

        this.state = {
            active: '',
            period: this.props.filter.period,
            periodDisplay: null,
            departments: this.props.filter.departments,
            departmentsDisplay: null
        };

        this.updateFilterDepartments = this.updateFilterDepartments.bind(this);
        this.updateFilterPeriod = this.updateFilterPeriod.bind(this);
        this.initDepartments = this.initDepartments.bind(this);
    }

    componentDidMount() {
        // Getting initial period
        if (!this.state.period && this.props.initialFrom && this.props.initialTo) {
            this.didSelectPeriod(this.props.initialFrom, this.props.initialTo);
        }

        else if (this.state.period) {
            let initPeriod = this.state.period.match(/.{1,2}/g);
            this.didSelectPeriod(
                initPeriod[2] + initPeriod[3] + '-' + initPeriod[1] + '-' + initPeriod[0], 
                initPeriod[6] + initPeriod[7] + '-' + initPeriod[5] + '-' + initPeriod[4]
            );
        }
    }

    componentWillUnmount(){
        this.setState({active: ''});
    }

    updateFilterPeriod() {
        this.props.updateFilterPeriod(this.state.period);
    }

    didSelectPeriod(rawFrom, rawTo) {
        if (rawFrom && rawTo) {
            let From = moment(rawFrom),
                To = moment(rawTo);

            if (From.diff(To, 'days') === 0) {
                this.setState ({
                    periodDisplay: From.format('D MMM YYYY'),
                    period: From.format('DDMMYYYY') + To.format('DDMMYYYY')
                }, () => this.updateFilterPeriod());
            }

            else {
                this.setState ({
                    periodDisplay: From.format('D MMM YYYY') + ' - ' + To.format('D MMM YYYY'),
                    period: From.format('DDMMYYYY') + To.format('DDMMYYYY')
                }, () => this.updateFilterPeriod());
            }
        }

        else {
            this.setState({
                periodDisplay: null,
                period: null
            }, () => this.updateFilterPeriod());
        }
    }

    updateFilterDepartments() {
        this.props.updateFilterDepartments(this.state.departments);
    }

    didSelectDepartments(hashes) {
        if (hashes.length === 0) {
            this.setState({
                departments: null,
                departmentsDisplay: null
            }, () => this.updateFilterDepartments());
        }

        else {
            this.setState({
                departments: hashes,
                departmentsDisplay: hashes.length + ' department(s) selected'
            }, () => this.updateFilterDepartments());
        }
    }

    render() {
        return (
            <div className='FILTER_wrapper' >
                <div className={classnames('FILTER_section', {active: this.state.active == 'datepicker'})} onClick={() => this.setState({active: 'datepicker'})}>
                    <i className="fa fa-calendar FILTER_icon FILTER_icon--datepicker"></i><div className="FILTER_input FILTER_input--datepicker">{this.state.periodDisplay || <div className="placeholder">Select a period</div>}</div>

                    <div className={classnames('FILTER_dropdown FILTER_dropdown--datepicker', {active: this.state.active == 'datepicker'})}>
                        <DatePicker className='' didSelect={(rawFrom, rawTo) => this.didSelectPeriod(rawFrom, rawTo)} />
                    </div>
                </div>

                <div className={classnames('FILTER_section', {active: this.state.active == 'departmentpicker'})} onClick={() => this.setState({active: 'departmentpicker'})}>
                    <i className="fa fa-tags FILTER_icon FILTER_icon--departmentpicker"></i><div className="FILTER_input FILTER_input--departmentpicker">{this.state.departments ? this.state.departmentsDisplay : <div className="placeholder">Select department(s)</div>}</div>

                    <div className={classnames('FILTER_dropdown FILTER_dropdown--departmentpicker', {active: this.state.active == 'departmentpicker'})}>
                        <DepartmentPicker didSelect={(hashes, names) => this.didSelectDepartments(hashes)} />
                    </div>
                </div>
            </div>
        );
    }
}

function mapStateToProps(state) {
    return { 
        departments: state.departments,
        filter: state.filter
    };
}

子组件(DepartmentFilter)

class DepartmentPicker extends Component {
    callCallback() {
        this.props.didSelect(this.state.selectedHash, this.state.selectedName);
    }

    handlePredefined(type) {
        let newStateHash = [];
        let newStateName = [];

        switch(type) {
        case 'all':
            this.props.departments.items.map(department => {
                newStateHash.push(department.hash);
                newStateName.push(department.name);
            });

            this.setState({
                selectedHash: newStateHash,
                selectedName: newStateName
            }, () => this.callCallback());
            break;


        case 'none':
            this.setState({
                selectedHash: newStateHash,
                selectedName: newStateName
            }, () => this.callCallback());
            break;
        }
    }

    handleDepartment(hash, name) {
        if (this.state.selectedHash.length > 0 && this.state.selectedHash.indexOf(hash) > -1) {
            let newStateHash = this.state.selectedHash;
            let newStateName = this.state.selectedName;
            let index = newStateHash.indexOf(hash);
            newStateHash.splice(index, 1);

            this.setState({
                selectedHash: newStateHash,
                selectedName: newStateName
            }, () => this.callCallback());
        }

        else {
            this.setState({
                selectedHash: [...this.state.selectedHash, hash],
                selectedName: [...this.state.selectedName, name]
            }, () => this.callCallback());
        }
    }

    renderDepartments(department) {
        let inSelection = this.state.selectedHash.length > 0 && this.state.selectedHash.indexOf(department.hash) > -1 ? true : false;

        return(
            <li className={classnames('DEPARTMENTPICKER_department', {active: inSelection})} key={department.hash} onClick={() => this.handleDepartment(department.hash, department.name)}>
                <i className={classnames('DEPARTMENTPICKER_icon', 'fa', {'fa-check': inSelection, 'fa-times': !inSelection})} style={{color: department.theme}}></i>
                {department.name}
            </li>
        );
    }

    render() {
        // Notice that here it waits for the end of the api call
        if (this.props.departments.isFetching) {return <Loader type='spinner' />;}

        return (
            <div className='DEPARTMENTPICKER_wrapper'>
                <ul className='DEPARTMENTPICKER_predefined'>
                    <li onClick={() => this.handlePredefined('all')}>All</li>
                    <li onClick={() => this.handlePredefined('none')}>None</li>
                </ul>

                <ul className="DEPARTMENTPICKER_departments">
                    {this.props.departments.items.map(this.renderDepartments)}
                </ul>

            </div>
        );
    }
}

function mapStateToProps(state) {
    return {
        departments: state.departments
    };
}

export default connect(mapStateToProps)(DepartmentPicker);

编辑 2 因为有些人询问应用程序遵循的流程,所以我决定将其包含在此处:

  1. 父组件渲染+API调用(登录触发)
  2. 子组件呈现(API 调用仍在进行中)
  3. API 调用完成 -> 需要为子组件创建初始状态

【问题讨论】:

  • 您可以使用componentWillMount 在组件渲染之前检查状态和/或进行 API 调用吗?
  • 不想听起来居高临下,但能够提炼出问题是一项重要的技能,尤其是在 SO 上。在大多数情况下,您也会在处理过程中得到答案。你能不能尝试显示一些代码?
  • 我已经提出了一个答案,但在我意识到我没有清楚地得到你的问题后将其删除:首先:React 的渲染是一个无副作用的功能,所以没有?*等待*ing,除非你延迟整个渲染。
  • 邮政编码。我们更容易看出您是否走在正确的道路上。
  • 第二:你的逻辑是错误的,因为如果它依赖于子状态,它就不是初始状态。 React 从上到下渲染。你必须引入一个临时的初始状态。如果您能提出一些澄清,我会尽力提供帮助。

标签: reactjs redux react-redux


【解决方案1】:

componentWillMount 只会触发一次,因此在此处设置状态不是一个好主意。但是如果你想设置一次状态,那么你可以使用 componentWillMount。否则有三种选择:

按开火顺序: 1)componentWillReceiveProps,props改变时调用 2)shouldComponentUpdate,这个生命周期函数将通过返回true或false来授予渲染或不渲染的权限。 3)ComponentWillUpdate,这将在调用渲染之前触发。您可以将新道具与旧道具和 setState 进行比较。

【讨论】:

    【解决方案2】:

    我快速浏览了一下。似乎您正在更新父级的 componentDidMount (通过 didSelectPeriod)中的状态。这保证了不必要的渲染。您应该将该代码移至

    • 构造函数,如果它是一次性初始化的东西
    • componentWillReceiveProps 如果您希望在相关道具更改时执行它。

    现在,通过这种方式,您可能会或不会摆脱错误消息,但您的代码存在更明显的问题:

    1. 您正在尝试在 redux 容器中呈现 redux 容器。好吧,这在技术上是可行的,但从架构的角度来看是非常错误的,并且可能是您问题的根源。试着想象一下,如果您只是将部门传递给您的子组件而不是使用连接的组件,您的代码会有多简单。请参考 Redux 手册中的 smart/dumb components。
    2. componentWillUnmount(){ this.setState({active: ''}); } 这不是必需的,因为组件的状态在卸载时会被删除。

    3. didSelect={(rawFrom, rawTo) =&gt; this.didSelectPeriod(rawFrom, rawTo)} 不要在每次渲染时都创建函数。改用:didSelect={this.didSelectPeriod}。不要忘记在构造函数中绑定:this.didSelectPeriod = this.didSelectPeriod.bind(this);

    4. 使用 redux 中间件(thunk、promise)将大大简化您的 API 调用。
    5. 尝试为您的组件添加更多上下文。它是布局、容器还是哑组件?大多数情况下,您只需要这三个,布局 > 容器 > 组件是您通常的渲染顺序。

    【讨论】:

    猜你喜欢
    • 2019-08-25
    • 2019-10-15
    • 1970-01-01
    • 1970-01-01
    • 2015-11-17
    • 1970-01-01
    • 2017-05-01
    • 2011-11-24
    • 1970-01-01
    相关资源
    最近更新 更多