数组状态更新后反应不重新渲染

Posted

技术标签:

【中文标题】数组状态更新后反应不重新渲染【英文标题】:React not re-rendering after array state update 【发布时间】:2020-01-13 04:02:08 【问题描述】:

我有一个从数组呈现的复选框列表 UI。更新数组后,复选框列表状态不会更新。

我移动了映射列表的代码,但它并没有改变结果。不会发生 DOM 重新渲染,请参见下面的 gif。

我一直在寻找,我发现这个问题已经报告了,但是关于将list.map 代码移出函数的解决方案对我不起作用。

您能建议我一个解决方案吗? 这个问题的根源是什么?

import React, useState  from "react"

import

    Box,
    DropButton,
    Grid,
    Text,
    Calendar,
    RangeInput,
    CheckBox
 from "grommet"

const CalButton = (  label,onSelect  ) =>

    return (
        <DropButton
            label= label 
            dropAlign=  top: 'bottom',left: 'left'  
            margin="small"
            dropContent=
                <Box pad="medium" background="ligth-3" elevation="small">
                    <Calendar range size="medium" onSelect= onSelect  />
                </Box>
             />
    )


const RangeButton = (  label,value,onChange,min,max,step,unit,header  ) =>


    return (
        <DropButton
            label= value === null ? label : value + ' ' + unit 
            dropAlign=  top: 'bottom',left: 'left'  
            margin="small"
            dropContent=
                <Box pad="medium"
                    background="ligth-3"
                    elevation="small"
                    align="center"
                >
                    <Text size="small"> header </Text>
                    <RangeInput
                        value= value 
                        min= min  max= max 
                        onChange= onChange 
                        step= step 
                    />
                    <Text weight="bold"> value </Text>
                    <Text weight="normal"> unit </Text>

                </Box>
             />
    )


const FeaturesButton = (  label,features,onChange  ) =>

    const FeaturesList = (  features,onChange  ) => (
        <>
             features.map( ( item,idx ) => (
            <CheckBox
                key= item.name 
                label= item.name 
                checked= item.checked 
                onChange= e => onChange( e,idx )  />) 
                )
            
        </>
    )



    return (
        <DropButton
            label= label 
            dropAlign=  top: 'bottom',left: 'left'  
            margin="small"
            dropContent=
                <Box pad="medium"
                    background="ligth-3"
                    elevation="small"
                    align="start"
                    direction="column"
                    gap="small"
                >
                    <FeaturesList 
                        features=features 
                        onChange=onChange />

                </Box>
             />
    )



const destApp = () =>


    const [ windStrength,setWindStrength ] = useState( null )
    const [ windFrequency,setWindFrequency ] = useState( null )
    const [ cost,setCost ] = useState( null )

    const date = new Date()
    const [ month,setMonth ] = useState( date.getMonth() )

    const [ listFeatures,setListFeatures ] = useState( [
        
            name: 'Butter flat water',
            checked: false,
        ,
        
            name: 'Moderately flat water',
            checked: false,
        ,
        
            name: '1-2m Waves',
            checked: false,
        ,
        
            name: '2m+ Waves',
            checked: false,
        ,
        
            name: 'Beginer Friendly',
            checked: false,
        ,
        
            name: 'Kite-in-kite-out',
            checked: false,
        ,
        
            name: 'Nightlife',
            checked: false,
        

    ] )
    const months = [ 'January','February','March','April','May','June','July','August','September','October','November','December' ];

    const updateFeaturesList = ( e,idx ) =>
    

        listFeatures[ idx ].checked = e.target.checked
        const newFeatures = listFeatures
        setListFeatures( newFeatures )
        console.log( "Updated features list",newFeatures,e.target.checked )
    

    return (
        <Grid rows= [ "xsmall","fill" ] 
            areas= [ [ "filterBar" ],[ "results" ] ] 
            gap="xxsmall">
            <Box gridArea="filterBar"
                direction="row-responsive"
                gap="xsmall"
                pad="xsmall"
                justify="center" >
                <CalButton label= months[ month ].toLowerCase()  onSelect= ( data ) => console.log( data )  />
                <RangeButton
                    label="wind strength"
                    header="What's your wind preference?"
                    min="15"
                    max="45"
                    unit="kts"
                    value= windStrength 
                    step= 1 
                    onChange= ( e ) =>
                    
                        setWindStrength( e.target.value )
                        console.log( windStrength )
                      />
                <RangeButton
                    label="wind frequency"
                    header="How often does your destination need to be windy?"
                    min="1"
                    max="7"
                    unit="days/week"
                    value= windFrequency 
                    step= 1 
                    onChange= ( e ) =>
                    
                        setWindFrequency( e.target.value )
                        console.log( windFrequency )
                      />
                <RangeButton
                    label="cost"
                    header="Average daily cost: 1 lunch, diner and doubble room at a midrange hotel?"
                    min="10"
                    max="400"
                    unit="€"
                    value= cost 
                    step= 1 
                    onChange= ( e ) =>
                    
                        setCost( e.target.value )
                        console.log( cost )
                      />

                <FeaturesButton
                    label="important features "
                    features= listFeatures 
                    onChange= updateFeaturesList 
                />
            </Box>
            <Box gridArea="results"
                margin="">
                Results go in here!

            </Box>


        </Grid>

    )


export default destApp 

【问题讨论】:

看起来你正在更新 listFeatures[ idx ].checked = e.target.checked 此处的变异状态并返回相同的对象,使用传播 (...) 运算符并设置像 setListFeatures( [...newFeatures] ) 这样的状态,这将返回应该触发的新对象重新渲染。 【参考方案1】:

您正在改变 updateFeaturesList 函数中的原始状态。使用setState 的函数形式更新您当前的功能列表:

const updateFeaturesList = (e, idx) => 
  const  checked  = e.target;
  setListFeatures(features => 
    return features.map((feature, index) => 
      if (id === index) 
        feature =  ...feature, checked ;
      

      return feature;
    );
  );
;

另请注意,在设置状态后立即调用console.log("Updated features list", newFeatures,e.target.checked) 不会显示更新的状态,因为设置状态是异步的。

【讨论】:

【参考方案2】:

问题出在updateFeaturesList,你直接在listFeatures[ idx ].checked = e.target.checked这一行改变状态,状态引用保持不变,所以react不知道它是否应该重新渲染。

您可以做的是在更改状态之前复制状态:

const updateFeaturesList = ( e,idx ) =>


    const newFeatures = [...listFeatures];
    newFeatures[ idx ].checked = e.target.checked
    setListFeatures( newFeatures )
    console.log( "Updated features list",newFeatures,e.target.checked )

【讨论】:

上帝保佑你兄弟!你救了我一整天!

以上是关于数组状态更新后反应不重新渲染的主要内容,如果未能解决你的问题,请参考以下文章

将新的商店状态放入道具后,反应不会重新渲染

仅在重新渲染组件时才反应状态更新

坐标更改后自定义标记不重新渲染

如何在反应中限制经常重新渲染的组件

防止子级在更新父级状态时重新渲染,使用父级方法作为本机反应的道具

编辑数组后在 React 中重新渲染子组件数组