首页 > 解决方案 > 存储更新时组件无法正确重新渲染

问题描述

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用于比较prevPropsthis.props),因为该组件甚至没有从mapStateToProps.

标签: androidreact-nativereduxreact-reduxredux-saga

解决方案


推荐阅读