繁体   English   中英

React Native:道具改变了,组件没有重新渲染

[英]React Native: Props changed, component didn't re-render

在一个动作之后,道具正在被改变。 componentWillUpdate 也会触发,但组件不会重新渲染。

看代码:

import React, {Component, PropTypes} from "react";
import {
    ActivityIndicator,
  Text,
  View,
  Image,
  NetInfo,
  Alert,
  TouchableOpacity,
  ScrollView,
  TextInput,
  Dimensions,
    RefreshControl,
  Platform
} from 'react-native';
import { styles, moment, GoogleAnalytics, KeyboardAwareScrollView, DeviceInfo, Loader, Accordion, I18n, CustomNavBar, DatePicker, FMPicker, CustomStarRating, Icon, CustomPicker, CustomQuestion, CustomDatePicker } from "../../common/components";
let { width, height } = Dimensions.get('window');
GoogleAnalytics.setTrackerId('UA-86421142-1');
GoogleAnalytics.trackScreenView('Evaluation Page');
GoogleAnalytics.setDispatchInterval(5);
var index = 0;

export default class Evaluation extends Component {
    static propTypes = {
        user: PropTypes.string.isRequired,
    users: PropTypes.object.isRequired,
        evaluation: PropTypes.object.isRequired,
    getEvaluation: PropTypes.func.isRequired,
        submitEvaluation: PropTypes.func.isRequired
    };

    constructor(props) {
    super(props);
    this.state = {
            isLoading: true,
            evaluationList: '',
            totalEval: 0,
            date: moment().format('DD-MM-YYYY'),
            isRefreshing: false
    };
        this.list = {};
  }

  componentDidMount(){
    this.loadData();
        NetInfo.isConnected.fetch().then(isConnected => {
      this.setState({
        isConnected: isConnected
      });
    });
    NetInfo.isConnected.addEventListener(
      'change',
      isConnected => {
        this.setState({
          isConnected: isConnected
        });
      }
    );
  }

    shouldComponentUpdate(nextProps, nextState){
        let shouldUpdate = false;
        const oldValue = this.props.evaluation[this.props.user];
        const newValue = nextProps.evaluation[nextProps.user];
        Object.keys(newValue).forEach((index)=>{
            if(!oldValue.hasOwnProperty(index)){
                shouldUpdate = true;
            }
        });
        Object.keys(oldValue).forEach((index)=>{
            if(!newValue.hasOwnProperty(index)){
                shouldUpdate = true;
            }
        });
        console.log('should component update?', shouldUpdate);
        return shouldUpdate;
    }

    randomNumber(){
    index++;
    return index;
  }

  async loadData(cb = ()=>{}){
        await this.props.getEvaluation(this.props.users);
    let {user, users, evaluation, getEvaluation} = this.props;
    let data = evaluation[user];
    let dsource = [];
        let list = {};
    Object.keys(data).forEach((e)=>{
            let currentEvaluation = data[e];
            let fields = [];
            list[currentEvaluation.evaluationId] = {};
            currentEvaluation.evaluationField.forEach((f)=>{
                fields.push({
                    ...f,
                    value: ''
                });
                list[currentEvaluation.evaluationId][f.field_name] = {
                    value: '',
                    required: f.required
                };
            });
      dsource.push({
                id: currentEvaluation.evaluationId,
                title: currentEvaluation.evaluationTitle,
                expire: currentEvaluation.evaluationExpire,
                image: currentEvaluation.evaluationImage,
                count: currentEvaluation.evaluationField.length,
                fields: fields
      });
    });
        this.list = list;
    this.setState({
            evaluationList: dsource,
            isLoading: false,
            totalEval: dsource.length,
    });
        this.forceUpdate();
        cb();
  }

    async getObjectToPost(evaluationID){
        let obj = this.list;
    return obj[evaluationID];
  }

    async changeValue(a,b,c,type){
        let list = this.list;
    if(type == 'date' || type == 'picker'){
      list[a][b].value = c;
    } else {
      let oldValue = this.getValue(a,b);
      if(oldValue != c){
                list[a][b].value = c;
            }
    }
        this.list = list;
  }

  async getValue(id, name){
        let list = this.list;
        return list[id][name].value;
  }

    async startEvaluationSubmission(user, users, id, data, cb){
        await cb(user, users, id, data, ()=>{
            Alert.alert(
                I18n.t("evaluation_submitted_title"),
                I18n.t("evaluation_submitted_desc")
            );
        });
    }

    async submitEvaluation(evalid){
        const {user, users, evaluation, getEvaluation, submitEvaluation} = this.props;
    let allRequiredEnetered = true;
    let objToPost = {};
    let answers = await this.getObjectToPost(evalid);
    for(let key in answers){
      objToPost[key]=answers[key].value;
      if(answers[key].required == true && answers[key].value == ''){
        allRequiredEnetered = false;
      }
    }
    if(allRequiredEnetered){

      objToPost = {
        result: objToPost
      };
      let stringifiedObject = JSON.stringify(objToPost);

      if(this.state.isConnected){
                this.startEvaluationSubmission(user, users, evalid, stringifiedObject, submitEvaluation);
      } else {
                //// Save evaluation to submit later.
        Alert.alert(
          I18n.t("offline_mode_title"),
          I18n.t("evaluation_offline")
        );
      }

    } else {
      Alert.alert(
        I18n.t("invalid_input_title"),
        I18n.t("please_fill_in")
      );
    }
  }

    renderQuestions(EvaluationFields,TotalEvaluationsCount,EvaluationID){ // evald.fields, evald.count, evald.id
    let tmdata = [];
    for(let n=0; n < TotalEvaluationsCount; n++){
      if(n > 0){
        tmdata.push(
        <View key={this.randomNumber()} style={styles.separator}></View>
        );
      }
      tmdata.push(
        <Text key={this.randomNumber()} style={styles.questionTitle} >{EvaluationFields[n].label}{EvaluationFields[n].required > 0 ? ' *' : ''}{'\n'}</Text>
      );
      switch (EvaluationFields[n].type) {
        case 'date':
        let currentValue = this.getValue(EvaluationID, EvaluationFields[n].field_name);
        let dateToShow = this.props.date;
        if(currentValue.length != undefined && currentValue.length != ''){
          dateToShow = currentValue;
        }
        tmdata.push(
          <View style={styles.datepicker} key={this.randomNumber()}>
          <CustomDatePicker
            mode="date"
            placeholder={I18n.t("select_date")}
            format="DD-MM-YYYY"
            minDate="01-01-2000"
            maxDate="01-01-2099"
            showIcon={false}
            confirmBtnText={I18n.t("confirm_button")}
            cancelBtnText={I18n.t("login_page_scan_cancel")}
            onDateChange={(date) => {this.changeValue(EvaluationID, EvaluationFields[n].field_name, date, 'date');}}
            required={EvaluationFields[n].required > 0 ? true : false}
          />
          </View>
        );
        break;
        case 'text':
        tmdata.push(
          <TextInput
            key={this.randomNumber()}
            style={[styles.textinput, Platform.OS == "android" ? { borderWidth: 0, height: 35 } : {}]}
            onChangeText={(text) => {this.changeValue(EvaluationID, EvaluationFields[n].field_name, text, 'text');}}
            maxLength = {Number(EvaluationFields[n].max_length)}
            autoCorrect={false}
            autoCapitalize={'none'}
            clearButtonMode={'always'}
            placeholder={I18n.t("evaluations_comment_field")}
          />
        );
        break;
        case 'rate':
        tmdata.push(
          <View key={this.randomNumber()} style={styles.starrating}>
            <CustomStarRating
              maxStars={Number(EvaluationFields[n].stars)}
              rating={Number(this.getValue(EvaluationID, EvaluationFields[n].field_name))}
              selectedStar={(rating) => {this.changeValue(EvaluationID, EvaluationFields[n].field_name, rating, 'rating');}}
              starSize={(width / (Number(EvaluationFields[n].stars))) > ( width / 10) ? ( width / 10) : (width / (Number(EvaluationFields[n].stars)))}
              required={EvaluationFields[n].required > 0 ? true : false}
            />
          </View>
        );
        break;
      }
      if(EvaluationFields[n].type == 'list'){
        if(EvaluationFields[n].widget == 'note'){
          tmdata.push(
            <View key={this.randomNumber()}>
              <CustomQuestion
                  evaluationId={EvaluationID}
                  fieldName={EvaluationFields[n].field_name}
                  allowedValues={EvaluationFields[n].allowed_values}
                  noteColors={EvaluationFields[n].note_colors}
                  onChange={(value)=>{ this.changeValue(EvaluationID, EvaluationFields[n].field_name, value, 'custom') }}
                  required={EvaluationFields[n].required > 0 ? true : false}
              />
            </View>
          );
        } else {
          let allowedValues = EvaluationFields[n].allowed_values;
          let Options=[];
          let LabelsForOptions=[];

          for(let r=0; r < allowedValues.length; r++){
            Options.push(allowedValues[r][0]);
            LabelsForOptions.push(allowedValues[r][1]);
          }
          tmdata.push(
            <View style={Platform.OS == "ios" ? styles.picker : styles.pickerSimple} key={this.randomNumber()}>
              <CustomPicker
                options={Options}
                labels={LabelsForOptions}
                onSubmit={(option) => {this.changeValue(EvaluationID, EvaluationFields[n].field_name, option, 'picker');}}
                confirmBtnText={I18n.t("confirm_button")}
                cancelBtnText={I18n.t("login_page_scan_cancel")}
                text={I18n.t("please_select_answer")}
                required={EvaluationFields[n].required > 0 ? true : false}
              />
            </View>
          );
        }
      }
    }
    return(
      <View key={this.randomNumber()}>{tmdata}</View>
    );
  }

    renderRow() {
        if(!this.state.isLoading){
    let eval_length = this.state.totalEval;
    let content = [];
    let evaluationList = this.state.evaluationList;

    for(let x=0; x < eval_length; x++){
      let evald = evaluationList[x];

      content.push(
        <View style={[styles.cardContainer, (x+1) == eval_length ? { marginBottom: 6 } : {}]} key={this.randomNumber()}>
          <View style={styles.cardHeader} >
            <View style={styles.headerImageContainer}>
              <Image style={styles.headerImage} source={{uri: evald.image}} />
            </View>
            <View style={{ margin: 5 }}>
              <Text style={styles.cardTitle}>{evald.title}</Text>
            </View>
          </View>
          <View style={{ padding: 5 }}>
            {this.renderQuestions(evald.fields, evald.count, evald.id)}
          </View>
          <View style={{ padding: 5 }}>
            <View style={styles.separator}></View>
            <Text style={styles.footerText}>{I18n.t("evaluations_mandatory")}{'\n'}{I18n.t("evaluations_desc_expire")} {evald.expire}</Text>
            <TouchableOpacity onPress={() => this.submitEvaluation(evald.id)} style={styles.submitButton} >
              <Text style={styles.buttonText}>{I18n.t("evaluations_submit_button")}</Text>
            </TouchableOpacity>
          </View>
        </View>
      );
    }
      return(
        <View>
          <KeyboardAwareScrollView>
            <View key={this.randomNumber()}>
              {content}
            </View>
          </KeyboardAwareScrollView>
        </View>
      );
    }
  }

    renderData(){
        if(this.state.totalEval > 0){
      return(
                <View style={styles.container} key={this.randomNumber()}>
            {this.renderRow()}
        </View>
      );
    } else {
      return(
        <View style={styles.errorContainer}>
          <View style={styles.error}>
            <Text style={styles.Errortext}>
              {I18n.t("evaluations_no_evaluation_available")}
            </Text>
          </View>
        </View>
      );
    }
  }

    render() {
        const {user, users, evaluation, getEvaluation} = this.props;
        return (
        <View style={styles.container}>
          <View style={{ width: width, height: Platform.OS == "ios" ? 64 : 54}}>
            <CustomNavBar
              width={width}
              height={Platform.OS == "ios" ? 64 : 54}
              title={I18n.t("evaluation_page_nav_title")}
              titleSize={18}
              buttonSize={15}
              background={"#00a2dd"}
              color={"#FFF"}
              rightIcon={"ios-person-outline"}
              rightIconSize={30}
              rightAction={()=> { this.props.openProfile(); }}
            />
          </View>
          <View style={{ height: Platform.OS == "ios" ? height - 114 : height - 130 }}>
            {!this.state.isLoading ?
              <ScrollView
              refreshControl={
                <RefreshControl
                  refreshing={this.state.isRefreshing}
                  onRefresh={this.loadData.bind(this)}
                  tintColor="#00a2dd"
                  title=""
                  titleColor="#00a2dd"
                  colors={['#00a2dd', '#00a2dd', '#00a2dd']}
                  progressBackgroundColor="#FFFFFF"
                />
              }
            >
              {this.renderData()}
            </ScrollView>
            :<ActivityIndicator
                        animating={true}
                        style={{ paddingTop: Platform.OS == "ios" ? (height - 114)/2 : (height - 130)/2 }}
                        color={'#00a2dd'}
                        size={'small'}
                    />}
          </View>
        </View>
    );
    }
}

控制台日志输出:

控制台日志输出

请问有什么解决办法吗?


更新:整个代码已添加到问题中。

提交评估后,道具发生变化。 提交的评估将从评估列表中删除,但仍会呈现。 通过 RefreshControl (Pulldown to refresh) 调用 loadData() 将正确重新渲染并且评估将被删除。

提前致谢。

我遇到了类似的问题,道具不会细细地列出我从父帐户呈现的项目。 所以我看了你的代码。

您目前发生的情况如下:

  • 您最初调用this.loadData(); componentDidMount
  • 随着您的道具更改,如果旧用户对象中不存在任何新用户详细信息,或者新用户对象中缺少旧用户对象中存在的任何新用户详细信息,则shouldComponentUpdate将返回 true。
  • 如果shouldComponentUpdate返回true ,则将触发componentWillUpdate并最终render

componentWillUpdate()未在您的代码中定义,并且componentDidMount仅被调用一次。 所以你的组件不知道获取新数据,因此render不会显示新数据。

简答

在不太了解您的应用程序的情况下,我会尝试添加一个调用this.loadDatacomponentWillUpdate函数,该函数将刷新您的状态并在您的loadData函数的最后一行使用forceUpdate触发重新渲染。

也许检查https://facebook.github.io/react/docs/react-component.html以查看在组件生命周期中触发了哪些功能。

长答案

然而,为了使您的应用程序更易于维护并且可能更快,您可能需要考虑将数据获取和渲染拆分为不同的组件。

由于您已经在使用 redux,因此最好在 redux 中处理所有数据获取。 所以你会有以下内容:

Redux 减速器

  • 使用getEvaluation()startEvaluationSubmission()等函数将评估加载/保存到服务器...
  • 提供状态state.evaluations

评估容器.jsx

  • 连接到状态并调用getEvaluation(userID)等函数
  • 将 state.evaluations 链接到它的 props(使用 redux connect),然后仅使用它需要渲染的评估来渲染EvaluationsList.jsx

评估列表.jsx

  • EvaluationContainer.jsx获取它自己的(并且只有那个)评估以显示为道具

这样,如果存储中的任何数据发生更改,容器将自动触发容器的更新并向下传输到显示组件。 您可能不需要覆盖 react 的 'shouldComponentUpdate' 函数,因为它非常好且非常有效。

Abhi Aiyer 写了一些关于 redux 的非常好的文章: https ://medium.com/front-end-developers/how-we-redux-part-1-introduction-18a24c3b7efe#.gr289pzbi

特别是第 5 部分可能对您很有趣: https : //medium.com/modern-user-interfaces/how-we-redux-part-5-components-bddd737022e1#.izwodhwwk

希望这有帮助......这就是我在不玩代码的情况下所能看到的。 让我知道你是怎么办的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM