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 未定义”