商店更新时,组件无法正确重新渲染

时间:2019-03-22 12:04:09

标签: android react-native redux react-redux redux-saga

我在屏幕上调用我的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接收新道具。

1 个答案:

答案 0 :(得分:0)

您的代码没有错。这正是它的行为方式。

当redux状态更改时,您的react组件不应重新渲染。更改redux状态只会更改道具。您可以根据新道具决定是否要重新渲染组件,这可以通过更改组件的状态来完成。

如果您想查看新道具是否正在显示,则应使用componentDidUpdate(prevProps) lifeCycle方法,而不要使用componentDidMount()。您可以按照以下步骤进行操作:

componentDidUpdate(prevProps) {
    if (prevProps.someProp != this.props.someProp) {
        console.log(this.props);
    }
}