【问题标题】:React native doesn't re-render with all new statesReact native 不会重新渲染所有新状态
【发布时间】:2020-12-07 01:50:59
【问题描述】:

我正在建立一个注册表单,但遇到了问题。用户完成字段后,我使用正则表达式进行验证检查。每个验证函数都会设置一条错误消息。即使我故意触发所有这些错误,但如果我提交表单,只会出现一个。这是我的代码:

function RegisterScreen(): JSX.Element{


    const pwInput = React.createRef();
    const [firstName, setFirstName] = useState('');
    const [lastName, setLastName] = useState('');
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const [confirmPassword, setConfirmPassword] = useState('');
    const [birthday, setBirthday] = useState<Date | null>(null);
    const [isSelected, setSelection] = useState(false); 
    const [openTerms, setOpenTerms] = useState(false);
    const [emailError, setEmailError] = useState<string | null>(null);
    const [passError, setPassError] = useState<string | null>(null);
    const [confPassError, setConfPassError] = useState<string | null>(null);
    const [isTermsChecked, setIsTermsChecked] = useState<string | null>(null); 
   

    function _validateEmail(): boolean{
        const re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
        if(re.test(email))
            return true;
        else{
            setEmailError(RegisterErrors.emailError);
            return false;
        }
       
    }
    function _validatePw(): boolean{
        const re = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$/
        
        if(re.test(password)) return true;
        else{
            setPassError(RegisterErrors.pwWeak);
            return false
        }
    }
    function _pwMatch(): boolean{

        if(password == confirmPassword){
            
            return true;
        }else{
            setConfPassError(RegisterErrors.pwConfirmMatchError);
            return false;
        }
    }

    function _validateAge(): boolean{
            return true;
    }

    function _validateTermsAndCond(): boolean{
            return true;
    } 

   

    function validateFieldsAndRegister(){

        console.log(email);
        console.log(password);
        console.log(confirmPassword);
        
        if( _validateEmail() && _validatePw() && _validateAge() && _validateTermsAndCond() && _pwMatch()){

                //trimite la firebase
                console.log("okay")

        }else{

           console.log('wrong fields')

            
        }

    }


  return(
        <SafeAreaView style={{flex:1}}>
     
        <ScrollView>
        <View style = {{flex:1, alignItems: "center", justifyContent:"center",backgroundColor:"white"}}>
        
        <TermsAndConditions open = {openTerms} close = { ()=>setOpenTerms(false) }/>
    
        <Image source={require("../../images/logoR.png")} style={styles.logoImg}/>
        

        <Text style={styles.mainText}>Register</Text>
        


        
          <Input  
            containerStyle={styles.inputView}
            inputContainerStyle={{backgroundColor:"white"}}
            placeholder="First Name..." 
            placeholderTextColor="rgb(0,0,0)"
            errorStyle={{ color: 'red' }}
            onChangeText={text => setFirstName(text)}/>
        
      
          <Input  
            containerStyle={styles.inputView}
            placeholder="Last Name..." 
            placeholderTextColor="rgb(0,0,0)"
            errorStyle={{ color: 'red' }}
            onChangeText={text => setLastName(text)}/>
        
       
        
          <Input 
            onFocus={()=>{setPassError(null)}}
            
            secureTextEntry
            containerStyle={styles.inputView}
            placeholder="Password..." 
            placeholderTextColor="rgb(0,0,0)" 
            errorStyle={{ color: 'red' }}
            errorMessage={passError}
            onChangeText={text =>setPassword(text)}/>
       

      
          <Input  
            secureTextEntry
            onFocus = {()=>{setConfPassError(null)}}
            containerStyle={styles.inputView}
            placeholder="Confirm Password..." 
            placeholderTextColor="rgb(0,0,0)"
            errorStyle={{ color: 'red' }}
            errorMessage={confPassError}
            
            onChangeText={text =>setConfirmPassword(text)}/>
      

       
          <Input  
            containerStyle={styles.inputView}
            onFocus={()=>{setEmailError(null)}}
            placeholder="Email..." 
            placeholderTextColor="rgb(0,0,0)"
            errorStyle={{ color: 'red' }}
            errorMessage={emailError}
            
            onChangeText={text => setEmail(text)}/>
       

         
        <View style={styles.checkboxContainer}>
            <CheckBox
            value={isSelected}
            onValueChange={()=>{setSelection(!isSelected)}}
            style={styles.checkbox}
          
            />
    
            <TouchableOpacity onPress = {()=> {setOpenTerms(true);}}>
            <Text style={styles.label}>Terms and Conditions</Text>
            </TouchableOpacity>

        </View>

        <TouchableOpacity  style={{...styles.inputView, ...styles.regBtnContainer}} onPress = {()=>{validateFieldsAndRegister()}}>
            <Text style = {styles.txtRegBtn}>Register</Text>
        </TouchableOpacity>
        

        </View>
        
</ScrollView>

</SafeAreaView>
    )
}

Screenshot

如果我在 validateFieldsAndRegister 中的 if 语句之前调用验证函数,它将起作用。 3 个字段会出现错误。

这是我显示的错误

export const RegisterErrors = {

    emailError: "You must enter a valid email",
    pwConfirmMatchError:"The passwords you entered are not the same",
    termsAndCondError:"You must read and agree with the Terms and Conditions",
    birthdayError:"You must be 21 or older",
    pwWeak:"Your password must contain: a lowercase letter, a acapital letter, a number and minimum 8 characters"

}

【问题讨论】:

  • 您是否考虑过状态更新的异步性质?这可能只是一个竞争条件。
  • 看起来只要验证函数返回 false,其余部分就不会执行,因为这样做没有意义.. 总体结果将是 false,因为我使用的是 AND 运算符。这是打字稿/javascript中的东西吗?
  • 是的,JS会短路。
  • 这是一个javascript的东西Short-circuit evaluation

标签: reactjs typescript jsx native


【解决方案1】:

这是一个 sn-p 说明正在发生的一些事情。它说明了更新状态的三种不同策略。

第一个将所有三个setState() 调用组合为一个函数调用,并且不使用if() 语句来检查结果。状态变化立即显示。

第二个将更新拆分为对async 函数的三个单独调用,这三个函数依次调用await 一个超时函数,然后再调用setState()。这些调用是在 OP 在if() 语句中进行的。每个异步函数都返回一个被评估为真实的 Promise,我们看到控制台记录了“promise”。超时的结果随后会慢慢渗入。

第三种情况最接近 OP 的问题。它还从if() 语句中进行单独调用,但它们是同步的并返回false。第一个调用完成它的setState() 调用,返回false 并停止来自if 内部的进一步调用。因为状态发生了变化,React 调用了一个渲染,我们看到了单个状态的变化。

所有这一切的结果是,最好在if() 语句之前进行验证调用,或者更好的是,收集所有验证结果,然后将所有setState() 调用分组到一个函数中。这将允许 React 更有效地对这些更改进行分组并尽可能少地调用渲染。

(抱歉,sn-p 混乱,内置 babel 似乎无法处理 async/await

.container {
  display: flex;
  
}
.example {
  margin: 1rem;
  padding: 1rem;
  border: 1px solid gray;
}
button {
  margin: 1rem;
}
<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
  const { useState } = React
  
  function App() {
  const [stateOne, setStateOne] = useState(1);
  const [stateTwo, setStateTwo] = useState(1);
  const [stateThree, setStateThree] = useState(1);
  
  const cycleStateSingle = () => {
    setStateOne((state) => state + 1);
    setStateTwo((state) => state + 1);
    setStateThree((state) => state + 1);
  }
  
  const [stateOnePromise, setStateOnePromise] = useState(1);
  const [stateTwoPromise, setStateTwoPromise] = useState(1);
  const [stateThreePromise, setStateThreePromise] = useState(1);
  
  const cycleOne = async () => {
      await new Promise(resolve => setTimeout(resolve, 1000));
      setStateOnePromise((state) => state + 1);
  }
  
  const cycleTwo = async () =>{
      await new Promise(resolve => setTimeout(resolve, 2000));
      setStateTwoPromise((state) => state + 1);
  }
  
  const cycleThree = async () =>{
      await new Promise(resolve => setTimeout(resolve, 3000));
      setStateThreePromise((state) => state + 1);
  }
  
  const cycleStatePromise = () => {
    if (cycleOne(true) && cycleTwo(true) && cycleThree(true)) {
      console.log('promise');
    }
  }
  
  const [stateOneStepped, setStateOneStepped] = useState(1);
  const [stateTwoStepped, setStateTwoStepped] = useState(1);
  const [stateThreeStepped, setStateThreeStepped] = useState(1);
  
  const cycleOneStepped = () => {
      setStateOneStepped((state) => state + 1);
      return false;
  }
  
  const cycleTwoStepped = () =>{
      setStateTwoStepped((state) => state + 1);
      return false;
  }
  
  const cycleThreeStepped = () =>{
    setStateThreeStepped((state) => state + 1);
    return false;
  }
  
  const cycleStateStepped = () => {
    if (cycleOneStepped() && cycleTwoStepped() && cycleThreeStepped()) {
      console.log('stepped');
    }
  }
  
  return (
  <div className="container">
    <div className="example">
      <div className="state-1">State 1: {stateOne}</div>
      <div className="state-2">State 2: {stateTwo}</div>
      <div className="state-3">State 3: {stateThree}</div>
      <button type="button" onClick={cycleStateSingle}>Single State Change</button>
    </div>
    <div className="example">
      <div className="state-1">State 1: {stateOnePromise}</div>
      <div className="state-2">State 2: {stateTwoPromise}</div>
      <div className="state-3">State 3: {stateThreePromise}</div>
      <button type="button" onClick={cycleStatePromise}>Promise State Change</button>
    </div>
    <div className="example">
      <div className="state-1">State 1: {stateOneStepped}</div>
      <div className="state-2">State 2: {stateTwoStepped}</div>
      <div className="state-3">State 3: {stateThreeStepped}</div>
      <button type="button" onClick={cycleStateStepped}>Stepped State Change</button>
    </div>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById('root'));
</script>

<div id="root"></div>

【讨论】: