观看 egghead.io 上的 "Use the key prop when Rendering a List with React"(The Beginner's Guide to ReactJS 的一部分)。
具体来说,试着改变主题,然后切换主题,注意输入字段的值并没有改变为更合理的主题。即使你输入 "我的公司需要培训 "这样的内容,然后将主题从 "培训 "改为 "问题",让它将主题重置为一个更好的默认值也是更合理的。
现在已经按预期工作了。这里是实现,我将强调其区别:
const defaultValuesByTopic = {
training: 'I would like some training',
consulting: 'I have consulting needs',
question: 'I have some questions',
}
function Contact() {
const [topic, setTopic] = React.useState('training')
return (
<form>
<label htmlFor="topic">Topic</label>
<select id="topic" value={topic} onChange={e => setTopic(e.target.value)}>
<option value="training">Training</option>
<option value="consulting">Consulting</option>
<option value="question">Question</option>
</select>
<label htmlFor="subject">Email Subject</label>
<input
id="subject"
key={topic}
defaultValue={defaultValuesByTopic[topic]}
/>
<label htmlFor="body">Email body</label>
<textarea id="body" />
</form>
)
}
这些实现之间的唯一区别是,工作的那个有一个key 的道具,而另一个没有。
我想和你分享一个小技巧,不是因为我经常使用这个(虽然这正是我在联系页面上做的),而是因为理解这个原理会帮助你更好地理解React。它与React组件的 "实例 "以及React如何对待key 道具有关。
我将向你展示的内容与元素/组件实例有很大关系,并且同样适用于上述<input />,以及你编写和渲染的组件。用组件的状态可能更容易理解,所以我们将从这个角度来讨论。
想象一下,你有一个管理内部状态的React组件。这些状态是附着在组件实例上的。这就是为什么你可以在页面上渲染该组件两次,而它们将完全独立运行。对于我们的演示,让我们使用非常简单的东西。
function Counter() {
const [count, setCount] = React.useState(0)
const increment = () => setCount(c => c + 1)
return <button onClick={increment}>{count}</button>
}
我们可以在页面上多次渲染这个东西,而且每次都是完全独立的。React将与每个单独的实例一起存储状态。当一个组件从页面上移走时,它不会影响其他的。如果你渲染一个新的,它也不会影响现有的组件。
你可能知道React的key prop是你在映射一个数组时需要放在元素上的东西(否则React会对你生气)。
题外话:如果你想知道为什么这是必要的,以及如果你忽视它或简单地把index 作为键会发生什么,请看"用React渲染列表时使用键道具"
React的key 道具让你有能力控制组件实例。每次React渲染你的组件时,它都会调用你的函数来检索新的React元素,它用来更新DOM。如果你返回相同的元素类型,它就会保留这些组件/DOM节点,即使所有的props都改变了。
关于这一点的更多信息,请阅读优化React重新渲染的一个简单技巧
上面 "所有 "这个词上的星号就是我想在这里谈的。这方面的例外是key 道具。这允许你返回完全相同的元素类型,但强迫React卸载之前的实例,并加载一个新的实例。这意味着当时存在于组件中的所有状态都被完全移除,而组件则被 "重新初始化",以达到所有目的。对于组件来说,这意味着React将对效果(或componentWillUnmount )进行清理,然后它将运行状态初始化器(或constructor )和效果回调(或componentDidMount )。
注意:效果清理实际上发生在新组件被安装之后,但在下一个效果回调运行之前。
下面是一个在计数器中工作的简单例子:
function Counter() {
console.log('Counter called')
const [count, setCount] = React.useState(() => {
console.log('Counter useState initializer')
return 0
})
const increment = () => setCount(c => c + 1)
React.useEffect(() => {
console.log('Counter useEffect callback')
return () => {
console.log('Counter useEffect cleanup')
}
}, [])
console.log('Counter returning react elements')
return <button onClick={increment}>{count}</button>
}
function CounterParent() {
// using useReducer this way basically ensures that any time you call
// setCounterKey, the `counterKey` is set to a new object which will
// make the `key` different resulting in React unmounting the previous
// component and mounting a new one.
const [counterKey, setCounterKey] = React.useReducer(c => c + 1, 0)
return (
<div>
<button onClick={setCounterKey}>reset</button>
<Counter key={counterKey} />
</div>
)
}
这里是渲染出来的。
这是一个有注释的例子,说明如果我点击计数器按钮,然后点击重置,会有什么记录:
// getting mounted
Counter called
Counter useState initializer
Counter returning react elements
// now it's mounted
Counter useEffect callback
// click the counter button
Counter called
Counter returning react elements
// notice the initializer and effect callback are not called this time
// click the reset button in the parent
// these next logs are happening for our new instance
Counter called
Counter useState initializer
Counter returning react elements
// cleanup old instance
Counter useEffect cleanup
// new instance is now mounted
Counter useEffect callback
结论
同样,这种情况也发生在本地表单元素的状态上(比如value ,甚至焦点)。key 这个道具不仅仅是为了摆脱当你试图渲染一个元素数组时恼人的React控制台错误(所有来自React的 "恼人的 "错误都是很棒的,可以帮助你避免bug,所以请不要忽略它们)。key 道具也可以成为控制React组件和元素实例的一个有用机制。
我希望这很有趣/有启发性。如果你想玩玩这些代码,我这里有一个codeandbox。 祝你玩得开心