【问题标题】:React props are updating automaticallyReact 道具正在自动更新
【发布时间】:2017-04-12 11:47:33
【问题描述】:

我正在制作一个分页组件,但我遇到了传递给这个分页组件的道具的问题。元素道具会自动更新,所以我不能使用 componentWillReceiveProps 来更新我的组件。

这是我的父组件对 Paginate 组件的渲染和回调:

class AlarmManagement extends React.Component{
    constructor(props){
        super(props)
        this.state = {
            alarms: undefined,
            nameIsOrdered: false,
            codeIsOrdered: true,
            priorityIsOrdered: false,
            alarmsPaginate: undefined,
        }
        this.orderByName = this.orderByName.bind(this)
        this.orderByCode = this.orderByCode.bind(this)
        this.orderByPriority = this.orderByPriority.bind(this)
        this.getAlarms = this.getAlarms.bind(this)
        this.getAlarmsPaginate = this.getAlarmsPaginate.bind(this)
    }

    componentWillMount(){
        this.getAlarms()
    }

    getAlarms(){
        $.ajax({
            type: 'GET',
            url: '/api/alarm-event-types/',
            headers: { 'Authorization': "Token " + localStorage.token },
            success: (alarms) => {
                this.setState({
                    alarms: alarms.sort((a, b) => a.code - b.code)
                })

            },
            error: (fail) => console.log(fail)
        })
    }

    getAlarmsPaginate(page, amount) {
        const newAlarms = this.state.alarms
        this.setState({ alarmsPaginate: newAlarms.slice(page, amount) });

    }

    componentWillReceiveProps({emergency}){
        if(emergency !== undefined && emergency !== null){
            let updateList = [],
                shouldUpdate = false
            emergency.forEach((el, index) => {
                if(el.update){
                    updateList.push(index)
                }
                if(el.update == "alarm"){
                    shouldUpdate = true
                }
            })
            if(shouldUpdate){
                this.getAlarms()
            }
            updateList.forEach((el) => {
                if(this.props.clearList){
                    this.props.clearList(el)
                }
            })
        }
    }

    orderByName(){
        let alarms = this.state.alarms
        if(this.state.nameIsOrdered === true){
            this.setState({
                alarms: alarms.sort((a, b) => {
                    let nameA = `${a.name.toUpperCase()}`, 
                        nameB = `${b.name.toUpperCase()}`
                    if (nameA > nameB) {
                        return 1;
                    }
                    if (nameA < nameB) {
                        return -1;
                    }

                    return 0;
                }),
                nameIsOrdered: false,
                codeIsOrdered: false,
                priorityIsOrdered: false
            })
        }
        else{
            this.setState({
                alarms: alarms.sort((a, b) => {
                    let nameA = `${a.name.toUpperCase()}`, 
                        nameB = `${b.name.toUpperCase()}`
                    if (nameB > nameA) {
                        return 1;
                    }
                    if (nameB < nameA) {
                        return -1;
                    }

                    return 0;
                }),
                nameIsOrdered: true,
                codeIsOrdered: false,
                priorityIsOrdered: false
            })
        }
    }

    orderByCode(){
        let alarms = this.state.alarms
        if(this.state.codeIsOrdered === true){
            this.setState({
                alarms: alarms.sort((a, b) => b.code - a.code),
                codeIsOrdered: false,
                nameIsOrdered: false,
                priorityIsOrdered: false
            })
        }
        else{
            this.setState({
                alarms: alarms.sort((a, b) => a.code - b.code),
                codeIsOrdered: true,
                nameIsOrdered: false,
                priorityIsOrdered: false
            })
        }
    }

    orderByPriority(){
        let alarms = this.state.alarms
        if(this.state.priorityIsOrdered === true){
            this.setState({
                alarms: alarms.sort((a, b) => b.priority - a.priority),
                nameIsOrdered: false,
                codeIsOrdered: false
            })
        }
        else{
            this.setState({
                alarms: alarms.sort((a, b) => a.priority - b.priority),
                nameIsOrdered: false,
                codeIsOrdered: false
            })
        }
        this.setState(prevState => ({
                priorityIsOrdered: !prevState.priorityIsOrdered,
        }))
    }

    render(){
        const alarms = this.state.alarms,
              alarmsPaginate = this.state.alarmsPaginate;
        return(
            this.props.user.groups == 1 ? (
                <div className="contactto-middle-content">
                    <ManagementMenu/>
                    <div className="management-content">
                        <div className="list-management-subtitle">ALARMES</div>
                        <button type="button" className="btn btn--save-attend-or-add-someone btn--color-tecno" onClick={() => browserHistory.push('/app/alarms/form/add')}>
                            <div><span className="btn--bold">+ ADICIONAR</span> ALARME</div>
                        </button>
                        {alarms && 
                            <div className="list-table">
                                <Paginate outerClass="paginate-wrapper" filterElements={this.getAlarmsPaginate} maxElements={5} elements={alarms} />
                                <div className="list-table-header">
                                    <div className="left list-table--200"><span className="icons-order clickable" onClick={this.orderByName}>Nome<Order width="15" height="10"/></span></div>
                                    <div className="left list-table--200"><span className="icons-order clickable" onClick={this.orderByCode}>Código<Order width="15" height="10"/></span></div>
                                    <div className="left list-table--200"><span className="icons-order clickable" onClick={this.orderByPriority}>Prioridade<Order width="15" height="10"/></span></div>
                                    <div className="list-table-body-column--action--2icons"></div>
                                </div>

                                    <div className="list-table-body scroll">
                                        {alarmsPaginate && alarmsPaginate.map((alarm) =>
                                            <AlarmRow key={alarm.code} alarm={alarm} getAlarms={this.getAlarms} channelId={this.props.channelId} iconDetail={this.refs["iconDetail"]}/>
                                        )}
                                    </div>

                            </div>
                        }

                    </div>
                </div>
            ):
                <div className="error">Página não pode ser acessada, pois você não é um administrador</div>
        )
    }
}

export default AlarmManagement

这是我的分页组件:

export default class Paginate extends React.Component{
    constructor(props){
        super(props)

        this.state = {
            pagesNumber: undefined,
            elements: undefined,
            elementsNumber: undefined,
            startNumber: undefined,
            endNumber: undefined,
            pos: undefined,
        }

        this.nextList = this.nextList.bind(this)
        this.previousList = this.previousList.bind(this)
    }

    previousList(){
        if(this.state.pos > 1){
            let pos = this.state.pos - 1
            this.changePage(pos)
        }

    }

    nextList(){
        if(this.state.pos < this.state.pagesNumber){
            let pos = this.state.pos + 1
            this.changePage(pos)
        }

    }

    changePage(pos){
        const newStartNumber = pos === 1 ? 0 : this.props.maxElements * (pos - 1);
        const newEndNumber = this.props.maxElements * pos > this.state.elementsNumber ? this.state.elementsNumber : this.props.maxElements * pos;
        this.props.filterElements(newStartNumber, newEndNumber);
        this.setState({
            pos: pos,
            startNumber: newStartNumber,
            endNumber: newEndNumber,
        });
    }

    componentWillReceiveProps(nextProps){
        console.log(nextProps.elements != this.props.elements ? 'different' : 'equal')
    }

    componentWillMount(){
        this.setState((prevState, props) => ({
            pagesNumber: props.elements ? Math.ceil(props.elements.length / props.maxElements) : 0,
            elements: props.elements,
            elementsNumber: props.elements.length,
            startNumber: 1,
            endNumber: props.maxElements,
            pos: 1,
        }));
        if(this.props.filterElements){
            this.props.filterElements(0, this.props.maxElements)
        }
    }

    render(){
        const elementsNumber = this.state.elementsNumber,
              startNumber = this.state.startNumber,
              endNumber = this.state.endNumber;
        return(
            <div className={this.props.outerClass}>
                {elementsNumber > this.props.maxElements &&
                <div className="left contactto-100">
                    {elementsNumber && <span className="left">{`${startNumber === 0 ? 1 : startNumber}-${endNumber} de ${elementsNumber}`}</span>}
                    <span className="paginate-arrow paginate-arrow-left" onClick={this.previousList}></span>
                    <span className="paginate-arrow paginate-arrow-right" onClick={this.nextList}></span>
                </div>
                }
            </div>
        )
    }
}

Paginate.defaultProps = {
    outerClass: 'paginate-wrapper'
}

Paginate.propTypes = {
    outerClass: React.PropTypes.string,
    filterElements: React.PropTypes.func.isRequired,
    maxElements: React.PropTypes.number.isRequired,
    elements: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
}

componentWillReceive props 中记录的元素是一样的。

根据帖子建议更新了代码,但是: 组件WillReceiveProps(nextProps){ console.log(nextProps.elements != this.props.elements ? 'different' : 'equal') // 仍然相等 }

怎么了?

提前致谢。

【问题讨论】:

    标签: reactjs pagination


    【解决方案1】:

    不清楚您的问题是什么,或者您的代码打算如何运行。但是,您在代码中做了一些非常不明智的事情...

    changePage(pos){
            this.state.startNumber = pos === 1 ? 0 : this.props.maxElements * (pos - 1);
            this.state.endNumber = this.props.maxElements * pos > this.state.elementsNumber ? this.state.elementsNumber : this.props.maxElements * pos;
            this.props.filterElements(this.state.elements, this.state.startNumber, this.state.endNumber);
            this.setState({ 
                pos: pos,
                startNumber: this.state.startNumber,
                endNumber: this.state.endNumber
            });
        }
    

    虽然您在函数末尾正确地使用 setState() 来更新状态,但您在此之前直接改变了状态。您可以通过使用临时变量来保存新状态或在 setState 调用中进行计算来解决此问题。例如。

    changePage(pos){
            const newStartNumber = pos === 1 ? 0 : this.props.maxElements * (pos - 1);
            const newEndNumber = this.props.maxElements * pos > this.state.elementsNumber ? this.state.elementsNumber : this.props.maxElements * pos;
            this.props.filterElements(this.state.elements, newStartNumber, newEndNumber);
            this.setState({
                pos: pos,
                startNumber: newStartNumber,
                endNumber: newEndNumber
            });
        }
    

    在构造函数或 componentWillReceiveProps() 方法中使用 props 来设置状态也是一种反模式。一般来说,在 React 应用程序中,我们希望有一个单一的事实来源——即所有数据都由一个单独的组件负责,而且只有一个组件负责。该组件负责将数据存储在其状态中,并通过 props 将数据分发给其他组件。

    当你这样做时......

    constructor(props){
            super(props)
    
            this.state = {
                elements: this.props.elements,
                ...
            }
            ...
        }
    

    父组件和子组件现在都在其状态中管理元素。如果我们在子节点中更新 state.elements,这些更改不会反映在父节点中。我们已经失去了单一的事实来源,通过我们的应用程序跟踪数据流变得越来越困难。

    在您的特定情况下,父组件有责任在其状态内维护元素。 Child 组件接收元素作为道具 - 它不应该将它们存储为状态或以任何方式直接更新元素。如果对 Child 的操作需要更新元素,则需要通过从 Parent 作为 props 传递给它的函数来完成。

    我上面用作示例的 changePage 函数可能会变成...

    changePage(pos){
            const newStartNumber = pos === 1 ? 0 : this.props.maxElements * (pos - 1);
            const newEndNumber = this.props.maxElements * pos > this.props.elements.length ? this.props.elements.length : this.props.maxElements * pos;
            this.props.filterElements(this.props.elements, newStartNumber, newEndNumber);
            this.setState({
                pos: pos,
                startNumber: newStartNumber,
                endNumber: newEndNumber
            });
        }
    

    但是,我们可以更进一步——为什么我们需要将 this.props.elements 传递给 filterElements 函数? Parent 组件必须已经有元素的引用;毕竟它一开始就把它给了我们!

    changePage(pos){
            const newStartNumber = pos === 1 ? 0 : this.props.maxElements * (pos - 1);
            const newEndNumber = this.props.maxElements * pos > this.props.elements.length ? this.props.elements.length : this.props.maxElements * pos;
            this.props.filterElements(newStartNumber, newEndNumber);
            this.setState({
                pos: pos,
                startNumber: newStartNumber,
                endNumber: newEndNumber
            });
        }
    

    在您的父组件中,您将更改函数...

    getAlarmsPaginate(alarms, page, amount) {
        this.state.alarmsPaginate = alarms.slice(page, amount)
        this.setState({ alarmsPaginate: this.state.alarmsPaginate })
    
    }
    

    进入...

    getAlarmsPaginate(page, amount) {
        const newAlarmsPaginate = this.state.alarms.slice(page, amount);
        this.setState({ alarmsPaginate: newAlarmsPaginate });
    }
    

    请注意,我们直接切片 this.state.alarms,而不是函数参数 - 而且我们不再改变我们的状态 - 而是专门使用 setState 函数。

    还有更多的情况是您在整个代码中不恰当地使用了 props 和 state - 我会仔细检查并确保您遵循我上面列出的指导方针 - 或者更好地阅读 React Documentation。当您遵循这些做法时,您很可能会发现您的问题得到解决,但如果没有,请在此处发布您修改后的代码,我将很乐意提供进一步的帮助。

    编辑/示例代码

    父组件:

    class Parent extends React.Component {
      constructor() {
        super();
    
        // our parent maintains an array of all elements in its state,
        // as well as the current page, and the number of items per page.
        // getSampleData can be seen in the fiddle - it just makes an array
        // of objects for us to render
        this.state = {
          allElements: getSampleData(),
          page: 1,
          numPerPage: 10
        };
      }
    
    
      // we pass this function as a prop to our Paginate component to allow
      // it to update the state of Parent
      setPage(pageNum) {
        this.setState({
            page: pageNum
        });
      }
    
      render() {   
    
        // get the appropriate elements from our own state   
        const firstItem = (this.state.page - 1) * this.state.numPerPage;
        const lastItem = this.state.page * this.state.numPerPage;
        const elementRender = 
          this.state.allElements
          .slice(firstItem, lastItem)
          .map(element => {
            return (
              <div key={element.itemNumber}>{element.itemName}</div>
            );
        });
    
        // numberOfElements, numPerPage and the setPage function from
        // Parent's state are passed
        // to the paginate component as props
        return (
            <div>
            <Paginate 
              numberOfElements={this.state.allElements.length} 
              numPerPage={this.state.numPerPage}
              setPage={this.setPage.bind(this)}
              />
            {elementRender}
          </div>
        );
      }
    }
    

    分页组件:

    class Paginate extends React.Component {
        render() {
        const numberOfButtons = Math.ceil(this.props.numberOfElements / this.props.numPerPage);
        const buttons = [];
    
        // make a first page button
        buttons.push(<button key={0} onClick={() => this.props.setPage(1)}>First Page</button>);
    
    
        let i = 0;
    
        // add a button for each page we need
        while (i < numberOfButtons) {
            const page = ++i;
            buttons.push(
            <button 
                key={i} 
              onClick={() => this.props.setPage(page)}
            >
              Page {i}
            </button>
          );
        }
    
        // add a last page button
        buttons.push(<button key={i+1} onClick={() => this.props.setPage(i)}>Last Page</button>);
        return (
            <div>
            {buttons}
          </div>
        );
      }
    }
    

    JSFiddle

    【讨论】:

    • 修复所有这些道具和不恰当的状态真的很难,我会​​用我的新代码编辑我的问题给你看
    • 您仍在为 Paginate 组件中的状态分配道具。我为您做了一个简单的演示,希望能更好地向您展示我的意思,我将编辑我的答案。
    猜你喜欢
    • 2017-12-26
    • 2020-01-16
    • 1970-01-01
    • 2017-11-28
    • 2017-12-28
    • 1970-01-01
    • 2021-09-22
    • 2021-08-31
    • 2020-10-10
    相关资源
    最近更新 更多