redux-saga 与 redux 一起使用会导致 render() 被调用两次

Posted

技术标签:

【中文标题】redux-saga 与 redux 一起使用会导致 render() 被调用两次【英文标题】:redux-saga used with redux causes render() to be called twice 【发布时间】:2019-12-03 07:50:03 【问题描述】:

在我的应用程序中合并 redux saga 时遇到问题。 我从教程中了解到,中间件将解析从应用程序调度的操作并处理一些异步操作(asycn 存储,api)。

然后在管道中放置另一个动作,触发reducer,然后更新状态。

流程是我的组件按钮点击触发了一个action dispatch,被saga中的watcher捕获,然后处理api调用,然后put完成发送数据到reducer更新状态。

如果我在我的组件中调度操作 FETCH_DATA,则 saga 将捕获该操作并处理数据获取,然后调用 updateprofile。这会调用 reducer 并进行处理。

这是我所期望的。但即使在遇到 FETCH_DATA 传奇之前,调用会到达 reducer,并且由于没有操作类型 FETCH_DATA 案例来处理它,它将返回默认状态,这会导致应用程序重新渲染。所以渲染发生了两次,这对我列表中的项目造成了一些问题。

我认为这也是我在一些文章中所读到的。如何摆脱这种重新渲染?

function* datafetch(action) 
 let  data  = yield call(loginApi, action.payload);
  yield put(updateProfile(data.profile));


export function* dataFetcherSaga() 
  yield takeLatest('FETCH_DATA', datafetch);


/reducer.js
const toDoListReducer = (state, action) => 
  switch (action.type) 
    case "UPDATE_APPREDUX_STATE":
      return 
        //some processing with data and state
      ;
      break;
    case "UPDATE_PROFILE":
      return 
        //some processing with data and state
      ;
      break;

    default:
      return state;
  

  return state;
;
export default toDoListReducer;



//action
export const fetchData = currentDay => 
  return 
    type: 'FETCH_DATA',
    currentDay: currentDay
  ;
;

export function updateProfile(profile) 
  return  type: 'UPDATE_PROFILE', payload: authParams ;

//组件

render()
return (
  <View style=styles.viewStyle>

      <SafeAreaView>
        <View style=styles.viewPadding>
          <View>
            <View style=styles.toDoViewStyle>
              <TextInput
                style=styles.toDoInputStyle
                placeholder="What you gonna do ?"
                onChangeText=text => 
                  this.newTask = text;
                
              />
              <TouchableOpacity
                onPress=() => 
                  this.props.updateTasks(
                    
                      taskID: new Date().getTime(),
                      taskDay: this.currentDay, //millisecond field to get a unique value
                      taskValue: this.newTask,
                      taskCompleted: false,
                      taskCompletedTime: null
                    ,
                    "addTask"
                  );
                
              >
                <Image
                  style=styles.addImage
                  source=require("../../assets/add.png")
                />
              </TouchableOpacity>
            </View>
            <Text>


              ↓ To Do Items ↓
            </Text>
            <SectionList
              style=styles.flatListStyle
              renderItem=( item ) => <ToDoListItem value=item />
              renderSectionHeader=( section:  title, data  ) => 
                if (data.length > 0) 
                  return (
                    <Text
                      style=
                        paddingTop: 5,
                        fontWeight: "bold",
                        fontStyle: "italic",
                        fontSize: 15,
                        color: title === "Completed Tasks:" ? "green" : "red",
                        textDecorationLine: "underline"
                      
                    >
                      title
                    </Text>
                  );
                
              
              stickySectionHeadersEnabled=false
              sections=[
                
                  title: "Completed Tasks:",
                  data: this.props.tasks.filter(tasks => 
                    return tasks.taskCompleted === true;
                  )
                ,
                
                  title: "InComplete Tasks:",
                  data: this.props.tasks.filter(tasks => 
                    return tasks.taskCompleted === false;
                  )
                ,
                ,
              ]
              keyExtractor=(item, index) => item + index
            />
          </View>
        </View>
      </SafeAreaView>

  </View>
);

//子项

class ToDoListItem extends React.Component 


    constructor(props) 
        super(props);
        //this.state =  checked: false ;
    

    selectItem = () => 

        let updatedObject = 
            taskID: this.props.value.taskID,
            taskCompleted: !this.props.value.taskCompleted,
        ;
        this.props.done(updatedObject);
    ;
    deleteItem = () => 

        let deletedObject = 
            taskID: this.props.value.taskID,
        ;
        this.props.delete(deletedObject);
    ;
    render() 
        return (
            <View style=styles.viewStyle>
                <View style=styles.checkBoxStyle>
                  <CheckBox
                     checkedCheckBoxColor="green"
                        onClick=this.selectItem
                        isChecked=this.props.value.taskCompleted/>
                </View>
                <View style=styles.inputTextViewStyle>
                    <Text
                        style=
                            this.props.value.taskCompleted
                                ? styles.completeDone
                                : styles.inComplete
                        >
                        this.props.value.taskValue
                    </Text>

                    this.props.value.taskCompleted && <Text style= fontSize: 11, fontStyle: "italic", color: "blue" >"\n""Completed @ " + this.props.value.taskCompletedTime</Text>

                </View>
                <View style=styles.deleteTextStyle>
                    <TouchableOpacity onPress=this.deleteItem>
                        <Image
                            style=styles.deleteImage
                            source=require('../../assets/delete.png')
                        />
                    </TouchableOpacity>
                </View>
            </View>
        );
    

【问题讨论】:

在获取数据时显示加载屏幕。这是预期的,因为最初商店没有任何数据。 感谢您调查此问题,我尝试显示加载屏幕,但它会在几分之一秒内出现和消失,我正在使用部分列表添加 this.props.data 中的项目使用 mapStatetoProps 设置(此数据来自 saga,它执行了从 reducer 更新状态的操作)。我看到列表闪烁了几分之一秒。 请添加您的组件。 我已经添加了组件。我是这个反应原生的新手。如果可能的话,你能帮我看看方法或子项目是否有问题。 【参考方案1】:

是的,当您调度任何操作时,这是预期的行为,它将首先转到 reducer 而不是任何中间件。最佳实践是如前所述显示加载屏幕,但如果你真的想保持渲染,那么你可以使用 shouldComponentUpdate() 组件生命周期方法,它需要两个参数 nextProps 和 nextState。这种方法基本上保持了重新渲染,这可以通过一个简单的 if 条件来实现,例如

shouldComponentUpdate (nextProps, nextState) 
    let shouldUpdate = true
    if ((nextProps.someProps === this.props.someProps )) 
        shouldUpdate = false
    
    return shouldUpdate

【讨论】:

我不确定我的实现是否正确,但对我来说,在第一次调用 shouldComponentUpdate 时,nextProps.tasks.length 为 0,this.props.tasks.length 为 1。在下一次调用中,nextProps.tasks.length 为 1,this.props.length 为 0。不知道发生了什么:( 我在这里有一个疑问,当 saga 使用 put 努力调度到 reduce 时,我将 state 视为 const toDoListReducer = (state, action) => 中的默认状态。状态只有在它第一次从组件派发的动作而不是从 saga 放置的动作中遇到减速器时才有价值。这是预期的行为吗? 没有收到您的最后一条评论,但我们的想法是为了进行 API 调用,我们需要调度一个带有有效负载的操作(这可以是来自组件的点击)。此操作将在 Saga 中监听,您将在其中获取有效负载并进行 API 调用。 API 调用的响应将由另一个操作作为有效负载分派。这个新动作将在 reducer 中被监听,你可以在其中改变你的 store,从而更新你的组件的状态,这将调用 render()。因此,您将创建 2 个动作,一个在 Saga 中监听,另一个在 reducer 中监听。希望这会有所帮助。 是的,这是流程,但问题是当第一个动作被分派到 saga 时,reducer 也得到它并导致重新渲染【参考方案2】:

为防止重新渲染,您可以为组件创建一个容器。在这个容器组件中,使用来自react-redux 的redux compose 方法和connect 方法,例如

const ContainerComponent = compose(
  connect(mapStateToProps),
  component-that-displays-on-loading
)(Component-that-needs-data-from-the-state);

compose 是一个高阶组件,它从右到左运行传入的函数。此模式跳过第一次渲染并按预期执行流程。希望这会有所帮助。

【讨论】:

以上是关于redux-saga 与 redux 一起使用会导致 render() 被调用两次的主要内容,如果未能解决你的问题,请参考以下文章

[Redux/Mobx] 你有使用过redux-saga中间件吗?它是干什么的?

将firebase与redux-sagas连接的正确方法是啥?

react-hot-loader 与外部 configureStore 使用 redux-saga 抛出“regeneratorRuntime 未定义”

从 redux-saga 函数访问 React 上下文值

多个 redux-sagas

为啥使用 Redux-Observable 而不是 Redux-Saga?