【问题标题】:React redux props lifecycle issueReact redux props 生命周期问题
【发布时间】:2021-03-20 04:09:51
【问题描述】:

我遇到了一个小问题,我确实解决了这个问题,但我真的在寻找更好的解决方案!

假设你有一个 Parent A 组件,它的作用是调度一个动作来获取数据。

class ParentA extends Component {
    constructor(props) {
        super(props)
        const { dispatch } = this.props;
        dispatch(actionRequest({ clientId: props.match.params.customerId }))
    }

    render() {
        const { customer, isFetching } = this.props;

        if(isFetching){
            return <Spinner />
        }

        if(!customer){
            return null
        }

        return <CustomerDetailsPage customerId={this.props.match.params.customerId} customer={customer} {...this.props} />

    }
}

export default connect(state => ({
    customer: getClient(state),
    isFetching: isClientFetching(state)
}))(ParentA)

这里没什么复杂的。

假设我在 api 调用之前在我的 saga 中调度了一个动作,将 isFetching 设置为 true,并在 api 调用成功或错误之后将 isFetching 返回到错误的。 当然,我对这个 reducer 的初始状态 isFetching 为 false。

我的动作创建者看起来像这样(不要注意包装减速器,重要的是不同的动作)

const setFetching = isFetching => state => state.set('fetching', isFetching)

export default createReducer(initialState, {
    [actionSuccess]: [setClient],
    [actionRequest]: [setFetching(true)],
    [actionFulfill]: [setFetching(false)],
})

问题,总结起来就是这样一个:当reducer处于初始状态时,没有问题,因为我将第一次放入获取的数据,所以在第一次渲染时它是null。

问题是当 ParentA 组件卸载时,redux 仍然存储以前的值。 所以当我回到ParentA时,connect函数中的选择器已经有了值。

它最终导致 ParentA 的子级的第一次渲染无用,因为 isFetching 是错误的,并且在我的示例中 customer 不是我刚才所说的 null。

最后它会导致一个无用的子渲染,但是想象一下 ParentA 的子获取自己的数据,然后它会导致从子获取 2 次!

我通过将 isFetchingcustomer 在树中更深地移动到孩子中解决了这个问题,但我想避免拆分我的道具并在 ParentB 中处理这个问题。 我无法记住 ParentA,因为 isFetching 确实在变化。 你最终会提出什么建议?

【问题讨论】:

    标签: reactjs redux react-redux redux-saga


    【解决方案1】:

    我不确定这是否是您正在寻找的,但这些提示可能会帮助您。

    1. 对于您的问题,最好将数据(customerisFetching)存储为类状态的一部分并让操作返回获取的值。
    2. 尝试使用生命周期方法来处理/操作您的状态。也就是说,您可以在 componentDidMount/componentDidUpdate 方法中执行相同的操作,而不是在构造函数中调度操作。
    3. 尝试从 React.PureComponent 而不是 React.Component 扩展您的类。 PureComponent 通过比较状态或道具是否已更改来防止不必要的重新渲染,从而使您的生活更轻松。然而,如果您从组件扩展您的类,则由您来管理使用 shouldComponentUpdate 生命周期方法的重新渲染。

    以下结构可能会帮助您解决当前的问题。

    import React, {PureComponent} from 'react'
    const DEFAULT_CUSTOMER = {id: ''}
    class ParentA extends PureComponent {
        constructor(props) {
            super(props)
            this.state = {
              customer: DEFAULT_CUSTOMER,
              isFetching: false,
            }
        }
        // Will be called once the component has been mounted successfully
        componentDidMount() {
          this.fetchCustomer()
        }
        //Incase your component doesn't un mount and just re-renders the content based on the client id
        // you might have to use the componentDidUpdate 
        // will get invoked everytime the props or the state changes
        componentDidUpdate(prevProps, prevState) {
          const { clientId: oldClientId } = prevProps
          const { clientId } = this.props
          // essential for you to do some sort of check to prevent an infinite loop
          if ( clientId !== oldClientId ) {
            //will reset the customer to the default customer
            this.setCustomer()
            this.fetchCustomer()
          }
        }
        // Set the value of isFetching
        setIsFetching => (isFetching) =
          this.setState({
            isFetching,
          })
        // Set the value of customer
        setCustomer => (customer = DEFAULT_CUSTOMER) =
          this.setState({
            customer,
          })
        // Dispatches the action to fetch customer
        fetchCustomer => async () = {
          try {
            const { match, dispatch } = this.props
            const { clientId } = match.params
            this.setIsFetching(true)
            // Assuming you are passing back the JSONIFIED response back
            const resp = await dispatch(actionRequest({ clientId, }))
            this.setCustomer(resp.body.customer)
            this.setIsFetching(false)
          } catch (e) {
            this.setIsFetching(false)
          }
          
        }
        render() {
            const { customer, isFetching } = this.state;
    
            if(isFetching){
                return <Spinner />
            }
    
            if(!customer.id){
                return null
            }
    
            return <CustomerDetailsPage customerId={this.props.match.params.customerId} customer={customer} {...this.props} />
    
        }
    }
    
    export default ParentA
    

    【讨论】:

      【解决方案2】:

      我知道这不会是一个令人满意的答案,但如果您使用带有钩子的功能组件,您会更轻松。 useEffect 挂钩非常强大,可以 100% 帮助您轻松解决这个问题。否则,在类组件中,您将需要利用 ComponentDidMount、ComponentWillUnmount 等生命周期钩子。

      【讨论】:

      • 谢谢休伯特!但是让我们假设我继续使用类组件,除了在 unmount 上破坏我的减速器状态之外,你会提出其他建议吗?必须有一个更好的解决方案和解释为什么我的组件 ParentA 渲染它的子组件,即使我的 reducer 在我的构造函数中触发时将 isFetching 设置为 true!
      • 我认为这是因为您在构造函数中将 Fetching 设置为 false 。这意味着在第一次渲染时,孩子将被渲染。您可以将 isFetching 设置为未定义,并且只有当 isFetching 为 = 未定义或 false 时才会呈现子项。我希望我做对了
      猜你喜欢
      • 2019-10-08
      • 2017-02-11
      • 1970-01-01
      • 1970-01-01
      • 2020-02-27
      • 1970-01-01
      • 1970-01-01
      • 2018-11-17
      相关资源
      最近更新 更多