React State 结构设计
React 中组件 state 状态治理是组件设计中的难点之一,如何设计 state 的构造。遵循以下准则能够保障 state 更新不呈现逻辑上的谬误,也能够防止不必要的 state 保护:
- 相干的状态组合成一个 group。当每次触发更新的时候须要更新两个 state 则这两个 state 能够尝试合并成一个 state【从单个值类型,变成 object 或者 Array 等类型】。
import {useState} from 'react';
function ComA() {
// bad case
const [x, setX] = useState<number>(0);
const [y, setY] = useState<number>(0);
// good case
const [position, setPosition] = useState({x: 0, y: 0});
return (
<div>
<div style={{
position: 'absolute',
backgroundColor: 'red',
borderRadius: '50%',
transform: `translate(${x}px, ${y}px)`,
left: -10,
top: -10,
width: 20,
height: 20,
}} />
</div>
);
}
- 避免出现竞态的 state,也就是说两个或多个 state 存在竞态,同一时刻有且仅有一个是真值。如果存在这种问题,则须要思考防止以后这种 state 的构造, 应用不同的值去辨别抵触的 state,这样就把多个抵触的 state 合并成 1 个 state,区别在于 value 的变动以及其代表的意义。
import {useState} from 'react';
// bad case
function ComA() {
// 示意编辑状态
const [isWritting, setIsWritting] = useState(true);
// 示意是否保留
const [isSave, setIsSave] = useState(fasle);
// 其余状态
const [isComplete, setIsComplete] = useState(false);
return (//...);
}
// 这两头 isWriting 和 isSave 是抵触的。也就是说两个 state 存在竞态,有且仅有一个是真值。// combine mutilate state ingroup
function ComB() {const [status, setStatus] = useState<'writing' | 'save' | 'complete'>('writing');
return (// ...);
}
- 防止多余的 state。如果一个 state 能够通过其余 state 的计算得出【.length, 取反异或等】,那么这个 state 就是不须要存在的。
export default function Form() {const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [fullName, setFullName] = useState('');
function handleFirstNameChange(e) {setFirstName(e.target.value);
setFullName(e.target.value + ' ' + lastName);
}
function handleLastNameChange(e) {setLastName(e.target.value);
setFullName(firstName + ' ' + e.target.value);
}
return (
<>
<h2>Let’s check you in</h2>
<label>
First name:{' '}
<input
value={firstName}
onChange={handleFirstNameChange}
/>
</label>
<label>
Last name:{' '}
<input
value={lastName}
onChange={handleLastNameChange}
/>
</label>
<p>
Your ticket will be issued to: <b>{fullName}</b>
</p>
</>
);
}
// fullname 齐全能够由 firstName 和 lastName 拼接进去,应用独自的 state 来保留计算结果是多余的。
- 防止反复的状态。如果 state 存在反复雷同的数据时,这部分反复的数据很难放弃同步更新。【个别是针对数组项的解决,data 保留在一个 state 中,而后又应用一个 state 保留选中或者编辑某项。这时候 data 中的数据更新,current 可能会被缓存到旧值】。须要防止这种反复。解决办法【防止保留反复的内容,而是保留找到指定数据的 id 或者索引】。
import {useState} from 'react';
const defaultData = [{ title: 'Tom', id: 0},
{title: 'Sam', id: 1},
{title: 'Dodo', id: 2},
{title: 'Piker', id: 3},
];
function ComA() {const [data, setData] = useState(defaultData);
const [current, setCurrent] = useState(data[0]);
function handleClick(item) {setCurrent(item);
}
function handleInput(id, value) {const newData = data.map((item)=>{if (id === item.id) {return { ...item, title: value};
}
return item;
})
}
return (
<>
<ul>
{data.map((item) => {
return (<li key={item.id}>
<input value={item.title} onChange= {({target})=>{handleInput(item.id, target.value); }}/>
<button onClick={()=>{ handleClick(item) }}> 选中 </button>
</li>
);
})
}
</ul>
<p> 以后选中:{current.title}</p>
</>
);
}
// 问题:当点击“选中”按钮后,current 保留了以后 item 的一个援用。接着编辑以后项的 title,发现并不会同步到 <p> 中展现。
解决办法 1 :
仔细查看代码能看进去,通过 handleInput 执行时,返回了新的对象更新 data 中的 item。只有略微批改一下 handleInput 的代码,同时更新 current 即可。
function handleInput(id, value) {const newData = data.map((item)=>{if (id === item.id) {// return { ...item, title: value};
const newItem = {...item, title: value};
setCurrent(newItem);
return newItem;
}
return item;
})
}
然而这种形式不能一劳永逸,其余函数中再批改其余属性数据,还得减少同样的逻辑。
解决办法 2 :保留 item 的 id 不要保留反复的数据内容。
import {useState} from 'react';
const defaultData = [{ title: 'Tom', id: 0},
{title: 'Sam', id: 1},
{title: 'Dodo', id: 2},
{title: 'Piker', id: 3},
];
function ComA() {const [data, setData] = useState(defaultData);
// 批改
const [currentId, setCurrentId] = useState(0);
const currentItem = data.find(({id}) => id === currentId);
function handleClick({id}) {setCurrentId(id);
}
function handleInput(id, value) {const newData = data.map((item)=>{if (id === item.id) {return { ...item, title: value};
}
return item;
})
}
return (
<>
<ul>
{data.map((item) => {
return (<li key={item.id}>
<input value={item.title} onChange= {({target})=>{handleInput(item.id, target.value); }}/>
<button onClick={()=>{ handleClick(item) }}> 选中 </button>
</li>
);
})
}
</ul>
<p> 以后选中:{current.title}</p>
</>
);
}
一劳永逸解决问题,保留 id。更新的时候组件会主动获取对应的数据项
- 避免出现过深的嵌套 state。深度嵌套的 state 不便于更新,更新时,须要一层一层的解构,重组成新的嵌套对象。如果能够尝试应用平铺的形式组织 state 构造。react 进行 state 更新时,援用类型数据须要应用新的援用构造进行更新【解构复制,批改对应 value】,如果嵌套层级过多,更新时解构层级越简单,容易出问题。
以这些准则作为 state 结构设计方法论,逐渐实现性感 & 正当的 React 组件!
本文由 mdnice 多平台公布