簡體   English   中英

為什么 React 組件在 props 更改時不會重新渲染(更改樣式)?

[英]Why React component doesn't re-render (change styles) when props changed?

我制作了一個TagItem JSXElement,當用戶點擊/觸摸時顏色會發生變化。

它接收一個 prop isSelected ,並且在內部selected了自己的 state ,初始值為 prop isSelected 它的樣式會根據selected的 state 而改變,但是當 prop isSelected值改變時,樣式不會改變......

我想出的原因是它的 state 即使 prop isSelected改變也不會改變。

我不知道為什么會這樣。 有人知道嗎?

代碼是這樣的。

type TagItemProps = {
      tag: Tag;
      isSelected: boolean;
      onSelect: (tag: Tag) => boolean;
      onDeselect: (tag: Tag) => void;
    };
    
const TagItem = ({ tag, isSelected = false, onSelect, onDeselect }: TagItemProps) => {
      const [selected, setSelected] = useState(isSelected);

      console.log("isSelected: ", isSelected);
      console.log("selected: ", selected);
    
      const handleSelect = useCallback(() => {
        if (!onSelect(tag)) return;
        setSelected(true);
      }, [onSelect, tag]);
    
      const handleDeselect = useCallback(() => {
        onDeselect(tag);
        setSelected(false);
      }, [onDeselect, tag]);
    
      return (
        <TouchableOpacity
          style={[styles.container, selected ? styles.selected : null]}
          onPress={selected ? handleDeselect : handleSelect}
        >
          <Text style={[styles.text, selected ? { color: '#fff' } : null]}>
            {capitalizeFirstLetter(tag.name)}
          </Text>
        </TouchableOpacity>
      );
};
    
export default TagItem;
    
const styles = StyleSheet.create({
      container: {
        flex: 0,
        backgroundColor: '#fff',
        borderRadius: 20,
        paddingHorizontal: 10,
        paddingVertical: 12,
        margin: 5,
      },
      selected: {
        backgroundColor: 'green',
      },
      text: {
        textAlign: 'center',
        fontWeight: 'bold',
      },
});

從上面可以看出,當 state selected為 true 時,應應用styles.selected

我通過登錄控制台確認 prop isSelected更改但未selected state。

console.log("isSelected: ", isSelected); // false
console.log("selected: ", selected);

有誰知道為什么會這樣?

這是將道具復制到本地 state 的經典案例——您通常不想這樣做,因為它會引入錯誤的表面積,包括您所看到的錯誤。 如果某些東西可用作道具 - 將其復制到本地 state 的目的是什么? 相反,您應該使用回調來更改該道具的 state 存在於祖先中的任何位置。 復制意味着您現在必須管理保持本地 state 和 prop 同步——這就是為什么通常首先復制是一種反模式。

state 在isSelected更改時不更新的原因是因為useState的參數只是它的初始值 根據設計,即使由於道具更改而發生重新渲染,state 項目也不會更新。 將其復制到本地 state 意味着您可以讓它們保持同步(常見的錯誤原因)。

兩種選擇:

選項 A

不要將道具復制到 state 中,因此您甚至不需要確保道具和內部 state 同步。 直接使用isSelected ,去掉state項。 要設置選定的 state,您需要從父組件向下傳遞到組件的 props 回調——它接受更改的值並更改該父組件中的 state。 這消除了道具和您正在渲染的實際事物之間的毫無意義的障礙。

選項 B

如果您出於某種原因必須保留 state 的副本,請確保在道具更改時更新 state 並附加效果。

type TagItemProps = {
      tag: Tag;
      isSelected: boolean;
      onSelect: (tag: Tag) => boolean;
      onDeselect: (tag: Tag) => void;
    };
    
const TagItem = ({ tag, isSelected = false, onSelect, onDeselect }: TagItemProps) => {
      const [selected, setSelected] = useState(isSelected);

      useEffect(() => {
          setSelected(isSelected )
     }, [isSelected ])

      console.log("isSelected: ", isSelected);
      console.log("selected: ", selected);
    
      const handleSelect = useCallback(() => {
        if (!onSelect(tag)) return;
        setSelected(true);
      }, [onSelect, tag]);
    
      const handleDeselect = useCallback(() => {
        onDeselect(tag);
        setSelected(false);
      }, [onDeselect, tag]);
    
      return (
        <TouchableOpacity
          style={[styles.container, selected ? styles.selected : null]}
          onPress={selected ? handleDeselect : handleSelect}
        >
          <Text style={[styles.text, selected ? { color: '#fff' } : null]}>
            {capitalizeFirstLetter(tag.name)}
          </Text>
        </TouchableOpacity>
      );
};
    
export default TagItem;
    
const styles = StyleSheet.create({
      container: {
        flex: 0,
        backgroundColor: '#fff',
        borderRadius: 20,
        paddingHorizontal: 10,
        paddingVertical: 12,
        margin: 5,
      },
      selected: {
        backgroundColor: 'green',
      },
      text: {
        textAlign: 'center',
        fontWeight: 'bold',
      },
});

只留下我最終得到的代碼(@Adam 的選項 A),它工作得很好。

type TagItemProps = {
  tag: Tag;
  isSelected: boolean;
  onSelect?: (tag: Tag) => boolean;
  onDeselect?: (tag: Tag) => void;
};

const TagItem = ({ tag, isSelected, onSelect, onDeselect }: TagItemProps) => {
  const handleSelect = useCallback(() => {
    onSelect && onSelect(tag);
  }, [onSelect, tag]);

  const handleDeselect = useCallback(() => {
    onDeselect && onDeselect(tag);
  }, [onDeselect, tag]);

  return (
    <TouchableOpacity
      style={[styles.container, isSelected ? styles.selected : null]}
      onPress={isSelected ? handleDeselect : handleSelect}
    >
      <Text style={[styles.text, isSelected ? { color: '#fff' } : null]}>
        {capitalizeFirstLetter(tag.name)}
      </Text>
    </TouchableOpacity>
  );
};

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM