android - 存储更新时组件无法正确重新渲染
问题描述
GET_REQUEST
我在屏幕中调用我的操作,我Saga
发出请求并触发我的GET_SUCCESS
操作,但是我的屏幕不会自行重新渲染,只有当我更新组件的状态时,才会触发重新渲染并且新的道具反映商店出现。
所以......我的商店显然运作良好,在 Reactotron 商店里已经满了,如下所示:
https://user-images.githubusercontent.com/27635248/54751638-52671b80-4bba-11e9-9f9b-b7eee7f3deba.png
我的user
减速机:
export const Types = {
GET_SUCCESS: 'user/GET_SUCCESS',
GET_REQUEST: 'user/GET_REQUEST',
GET_FAILURE: 'user/GET_FAILURE',
UPDATE_USER: 'user/UPDATE_USER',
REHYDRATE_SUCCESS: 'user/REHYDRATE_SUCCESS',
REHYDRATE_FAILURE: 'user/REHYDRATE_FAILURE',
}
const INITIAL_STATE = {
data: {},
status: {
loading: false,
error: null,
}
}
export default function user(state = INITIAL_STATE, action) {
switch (action.type) {
case Types.GET_SUCCESS: {
return {
...state,
data: action.payload.user,
status: {
...state.status,
loading: false,
error: null
}
}
}
case Types.GET_REQUEST: {
return {
...state,
status: {
error: null,
loading: true
}
}
}
case Types.GET_FAILURE: {
if (Object.keys(state.data).length) {
action.payload.isStateEmpty = false
return {
...state,
status: {
...state.status,
loading: false
}
}
} else {
return {
...state,
status: {
...state.status,
loading: false
}
}
}
}
case Types.REHYDRATE_SUCCESS: {
return {
...state,
data: action.payload.user,
status: {
error: null,
loading: false
}
}
}
case Types.REHYDRATE_FAILURE: {
return {
...state,
status: {
loading: false,
error: action.payload.error
}
}
}
case Types.UPDATE_USER: {
return {
...state,
data: {
...state.data,
...action.payload.data
}
}
}
default: {
return state
}
}
}
export const Creators = {
getUserSuccess: user => ({
type: Types.GET_SUCCESS,
payload: {
user,
},
}),
getUserRequest: () => ({
type: Types.GET_REQUEST,
payload: {}
}),
getUserFailure: error => ({
type: Types.GET_FAILURE,
payload: {
error,
isStateEmpty: true
}
}),
rehydrateUserSuccess: user => ({
type: Types.REHYDRATE_SUCCESS,
payload: {
user
}
}),
rehydrateUserFailure: error => ({
type: Types.REHYDRATE_FAILURE,
payload: {
error
}
}),
updateUser: data => ({
type: Types.UPDATE_USER,
payload: {
data
}
}),
}
我的user
传奇:
import { Types } from '../ducks/user'
import { call, put, takeLatest } from 'redux-saga/effects'
import { Creators as UserActions } from '../ducks/user'
import API from 'utils/api'
import DAO from '../../utils/dao';
function formatUsuarioToDbKeys(usuario, pk_pessoa) {
return {
pk_user: usuario.id,
fk_pessoa: pk_pessoa,
username_usu: usuario.username,
email_usu: usuario.email,
first_name_usu: usuario.first_name,
las_name_usu: usuario.last_name,
ativo_usu: usuario.ativo,
}
}
function formatPessoaToDbKeys(pessoa) {
return {
pk_pessoa: pessoa.pessoa_id,
fk_funcao: pessoa.funcao_id,
nome_pes: pessoa.nome,
codigo_erp_pes: pessoa.codigo_erp,
ativo_pes: pessoa.ativa,
}
}
function formatFuncaoToDbKeys(funcao) {
return {
pk_funcao: funcao.id,
nome_fun: funcao.nome,
}
}
function formatEquipeToDbKeys(equipe) {
return {
pk_equipe: equipe.id,
nome_equ: equipe.nome,
ativo_equ: equipe.ativo,
}
}
function* getUser() {
try {
const dados = yield call(API.getInstance().getRequest, '/initializer/?tipo=1')
const funcao = yield call(formatFuncaoToDbKeys, dados.funcao)
const pessoa = yield call(formatPessoaToDbKeys, dados.pessoa)
const usuario = yield call(formatUsuarioToDbKeys, ...[dados.usuario, dados.pessoa.pessoa_id])
const equipe = yield call(formatEquipeToDbKeys, dados.equipe)
yield put(UserActions.getUserSuccess({ usuario, pessoa, funcao, equipe }))
yield call(DAO.getInstance().createFuncao, funcao)
yield call(DAO.getInstance().createPessoa, pessoa)
yield call(DAO.getInstance().createUsuario, usuario)
yield call(DAO.getInstance().createEquipe, equipe)
} catch (error) {
yield put(UserActions.getUserFailure('Ocorreu um erro ao buscar dados do usuário.'))
yield call(console.warn, error.message)
}
}
function* rehydrateUser(action) {
if (action) {
try {
const user = yield call(DAO.getInstance().getUsuario)
yield put(UserActions.rehydrateUserSuccess(user))
} catch (error) {
yield put(UserActions.rehydrateUserFailure('Ocorreu um erro ao buscar dados do usuário.'))
yield call(console.warn, error.message)
}
}
}
export default sagas = [
takeLatest(Types.GET_REQUEST, getUser),
takeLatest(Types.GET_FAILURE, rehydrateUser)
]
最后,这是我的组件:
class Dashboard extends Component {
...state and others methods
componentDidMount() {
this.props.getUserRequest()
this.props.getConstructionsRequest()
}
render() {
const spin = this.state.spin_value.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg']
})
return (
<View style={{ flex: 1 }}>
<StatusBar
hidden={false}
backgroundColor={colors.blueDarker} />
<ScrollView
ref={'refScrollView'}
stickyHeaderIndices={[2]}
style={styles.container}>
<ImageBackground
source={require('imgs/capa_dashboard.png')}
blurRadius={4}
style={styles.imageBackground}>
<View style={styles.headerContainer}>
<Text style={styles.textVersion}>{require('../../../package.json').version}</Text>
<Animated.View style={{ transform: [{ rotate: spin }] }}>
<TouchableOpacity
onPress={() => {
this._spinIcon()
this._handleRefresh()
}}>
<MaterialIcons
name={'refresh'}
size={35}
style={styles.refreshIcon}
color={'white'} />
</TouchableOpacity>
</Animated.View>
</View>
<View style={styles.headerButtonsContainer}>
<HeaderLeft
navigation={this.props.navigation} />
<Image
style={styles.image}
source={require('imgs/logo_header.png')} />
<HeaderRight />
</View>
<View style={{ flex: 1, justifyContent: 'center' }}>
{!this.props.user.status.loading && !!Object.keys(this.props.user.data).length
? <View>
<Text style={[styles.textNome, { textAlign: 'center' }]}>{this.props.user.data.pessoa.nome_pes}</Text>
<Text style={styles.textAuxiliar}>{this.props.user.data.funcao.pk_funcao == 1 ? this.props.user.data.equipe.nome_equ : 'Engenheiro(a)'}</Text>
</View>
: <ActivityIndicator style={{ alignSelf: 'center' }} size={'large'} color={colors.blue} />
}
</View>
</ImageBackground>
<ActionsButtons
stylesButton1={{ borderBottomColor: this.state.button_obras_selected ? 'white' : colors.transparent }}
stylesText1={{ color: this.state.button_obras_selected ? 'white' : 'lightgrey' }}
numberButton1={this.props.count_obras}
callbackButton1={async () => this.setState({ button_obras_selected: true })}
stylesButton2={{ borderBottomColor: !this.state.button_obras_selected ? 'white' : colors.transparent }}
stylesText2={{ color: !this.state.button_obras_selected ? 'white' : 'lightgrey' }}
numberButton2={this.state.count_enviar}
callbackButton2={async () => this.setState({ button_obras_selected: false })}
/>
<View>
{this.state.button_obras_selected
? <View style={styles.containerTextInput} >
<TextInput
onEndEditing={() => this._handleEndSearch()}
onFocus={() => this._handleStartSearch()}
ref={'textInputRef'}
style={styles.textInput}
onChangeText={search_text => this._filterSearch(search_text)}
autoCapitalize="none"
onSubmitEditing={() => this._handleClickSearch()}
underlineColorAndroid='rgba(255,255,255,0.0)'
placeholder={'Buscar obras...'} />
<TouchableOpacity
onPress={() => this._handleClickSearch()}
style={styles.searchImage}>
<MaterialCommunityIcons
name={'magnify'}
size={30}
color={'black'} />
</TouchableOpacity>
</View >
: null}
</View>
<View>
{this.state.button_obras_selected
? !this.props.constructions.status.loading && !!Object.keys(this.props.constructions.data).length
? <View style={{ flex: 1, marginTop: 8 }}>
<FlatList
data={this.props.constructions.data}
removeClippedSubviews={true}
keyExtractor={item => item.pk_obra.toString()}
renderItem={obra =>
<ObraComponent
callback={this._callbackObra}
item={obra.item}
isCurrentObra={nome => this.state.obra_atual == nome}
/>
}
/>
</View>
: <ActivityIndicator style={{ marginTop: 150 }} size={50} color={colors.blueDarker} />
:
<View style={{
flex: 1,
marginTop: 8,
}}>
<FlatList
data={[]}
removeClippedSubviews={true}
keyExtractor={item => item.fk_obra.toString()}
renderItem={acao =>
<AcaoComponent item={acao.item} />
}
/>
</View>
}
</View>
</ScrollView>
</View>
)
}
}
const mapStateToProps = state => ({
user: state.user,
constructions: state.constructions,
count_obras: state.constructions.data.length
})
const mapDispatchToProps = dispatch =>
bindActionCreators({
...UserActions,
...ConstructionsActions.DEEP,
...ConstructionsActions.SHALLOW
}, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(Dashboard)
正如我之前所说,我调用 getUserRequest()
触发GET_REQUEST
操作componentDidMount()
但是,当请求完成并且我的 redux 存储更新时,我的屏幕保持这样加载,即使存储中的加载状态是false
https://user-images.githubusercontent.com/27635248/54752551-4e88c880-4bbd-11e9-9164-5fad46db6c13.png
我在其他项目中使用过非常相似的 store 和 saga 结构,但由于某种原因,在这个特定的项目中,我无法弄清楚
当我按下其中一个按钮时,组件会更新它的状态并触发重新渲染,然后最终来自我的商店的数据在审查区域中显示为道具 - 这是我想出重新渲染东西的唯一方法使用 redux(至少在这个项目中),通过使用以下方式更新组件状态来强制重新渲染this.setState()
:
https://user-images.githubusercontent.com/27635248/54752771-fef6cc80-4bbd-11e9-9d15-a46acb87f3a4.png
我不确定我的实现有什么问题,我检查了其他类似的问题,它们似乎都不起作用,如果有人有建议,欢迎在下面发布,提前谢谢你。
"axios": "^0.18.0",
"react-native": "~0.55.2",
"react-redux": "^6.0.0",
"reactotron-redux": "^2.1.3",
"reactotron-redux-saga": "^4.0.1",
"redux": "^4.0.1",
"redux-saga": "^1.0.1",
编辑:
所以,据我了解,更新 redux 的商店反映了连接组件的新道具,而 React 确实基于浅比较来更新它们。我进行了一些实验,由于某种原因,我的操作被触发,sagas 运行良好,并且商店正在成功更改,但mapStateToProps
在我的成功操作被触发后没有被调用。基本上我无法选择组件是否应该更新(shouldComponentUpdate
用于比较prevProps
和this.props
),因为该组件甚至没有从mapStateToProps
.
解决方案
推荐阅读
- excel - 如何使用 VBA Excel 从 IP-Cam 获取 IsMotion 状态
- python - 可视化在 matplotlib/seaborn 中有意义的数值与分类数据
- vb.net - Auth0 LoginAsync() 身份验证窗口未将值传递回应用程序
- c# - LINQ,转换字典
到字典 - c# - 如何将 CDATA 参数包含到 C#soap 调用中
- firebase - 我可以使用 gcloud 而不是 Firebase CLI 部署我的 Firebase 托管网页吗?
- java - 列出Java中特定类的所有子类
- html - 如何拆分按钮内的文本
- ruby-on-rails - Mongoid 动态 store_in
- scala - 无法使用 Spark SQL 正确读取 Impala 视图