React道具会自动更新 [英] React props are updating automatically

查看:62
本文介绍了React道具会自动更新的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在制作一个分页组件,而传递给该分页组件的道具有问题.元素道具会自动更新,因此我无法使用componentWillReceiveProps来更新我的组件.

I am making a paginate component, and I'm having problems with the props passed to this paginate component. The elements props are updating automatically, so I cannot use componentWillReceiveProps to update my component.

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

Here is my parent component render and callback to Paginate's component:

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道具上记录的元素相同.

The elements logged at componentWillReceive props are the same.

代码根据发布建议进行了更新,但是: componentWillReceiveProps(nextProps){ console.log(nextProps.elements!= this.props.elements?'different':'equal')//仍然相等 }

Code updated following the post advices, but: componentWillReceiveProps(nextProps){ console.log(nextProps.elements != this.props.elements ? 'different' : 'equal') // still are equal }

怎么了?

谢谢.

推荐答案

不清楚您的问题是什么,或者代码的预期功能如何.但是,您在代码中做了一些非常不可取的事情……

It is not very clear what your question is, or how your code is intended to function. However, you are doing some very inadvisable things within your code...

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调用中的计算来对此进行补救.例如.

Although you are correctly using setState() to update state at the end of the function, you are directly mutating state before this. You can remedy this by either using a temporary variable to hold the new state, or by doing the calculations within the setState call. E.g.

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()方法中使用道具来设置状态也是一种反模式.通常,在React应用程序中,我们希望有一个真实的来源-即所有数据都是一个组件的责任,而只有一个组件的责任.该组件负责将数据存储在其状态内,并通过prop将数据分发给其他组件.

The use of props within the constructor or componentWillReceiveProps() methods to set state is also something of an anti-pattern. In general in a React application we want to have a single source of truth - i.e. all data is the responsibility of one single component, and only one component. It is the responsibility of this component to store the data within its state, and distribute the data to other components via props.

执行此操作时...

constructor(props){
        super(props)

        this.state = {
            elements: this.props.elements,
            ...
        }
        ...
    }

父级和子级组件现在都处于其状态内的管理元素.如果我们在子级中更新state.elements,则这些更改 not 不会反映在父级中.我们已经失去了唯一的真理来源,并且越来越难以通过我们的应用程序跟踪数据流.

The parent and child component are now both managing elements within their state. If we update state.elements in the child, these changes are not reflected in the parent. We have lost our single source of truth, and it becomes increasingly difficult to track the flow of data through our application.

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

In your specific case, it is the responsibility of the Parent component to maintain elements within its state. The Child component receives elements as props - it should not store them as state or directly update elements in any way. If an action on Child should require an update of elements, this needs to be done via a function passed to it from Parent as props.

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

The changePage function I used as an example above could then become...

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函数?父级组件必须已经具有对元素的引用;毕竟它首先给了我们!

However, we can go even further - why do we need to pass this.props.elements to the filterElements function? The Parent component must already have a reference to elements; after all it gave it to us in the first place!

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
        });
    }

然后在父组件中更改功能...

In your Parent component you would then change the function...

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函数.

Note we are slicing this.state.alarms directly, rather than a function argument - also we are no longer mutating our state - but using the setState function exclusively.

还有另外一些实例,在这些实例中,您在整个代码中不恰当地使用道具和状态-我将仔细阅读所有内容,并确保您遵循上面列出的准则-甚至最好阅读

There are several more instances where you use props and state inappropriately throughout your code - I would go through it all and ensure that you follow the guidelines I have set out above - or even better go and read the React Documentation. You may well find that your issue resolves when you follow these practices, but if not then post back here with your revised code and I will be glad to help further.

父项:

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

这篇关于React道具会自动更新的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆