react基础篇四
列表 & Keys
渲染多个组件
你可以通过使用{}
在JSX内构建一个元素集合
下面,我们使用Javascript中的map()
方法遍历numbers
数组。对数组中的每个元素返回<li>
标签,最后我们得到一个数组listItems
我们把整个listItems
插入到ul
元素中,然后渲染进DOM:
ReactDOM.render( <ul>{listItems}</ul>, document.getElementById('root') );
function NumberList(props) { const numbers = props.numbers; const listItems = numbers.map((number) => <li key={number.toString()}> {number} </li> ); return ( <ul>{listItems}</ul> ); } const numbers = [1, 2, 3, 4, 5]; ReactDOM.render( <NumberList numbers={numbers} />, document.getElementById('root') );
一个元素的key最好是这个元素在列表中拥有的一个独一无二的字符串。通常,我们使用来自数据的id作为元素的key
如果列表项目的顺序可能会变化,我们不建议使用索引来用作键值,因为这样做会导致性能的负面影响,还可能引起组件状态问题。如果你想要了解更多,请点击深度解析key的必要性。如果你选择不指定显式的键值,那么React将默认使用索引用作为列表项目的键值。
键(key)只是在兄弟之间必须唯一
数组元素中使用的key在其兄弟之间应该是独一无二的。然而,它们不需要是全局唯一的。当我们生成两个不同的数组时,我们可以使用相同的键
function Blog(props) { const sidebar = ( <ul> {props.posts.map((post) => <li key={post.id}> {post.title} </li> )} </ul> ); const content = props.posts.map((post) => <div key={post.id}> <h3>{post.title}</h3> <p>{post.content}</p> </div> ); return ( <div> {sidebar} <hr /> {content} </div> ); } const posts = [ {id: 1, title: 'Hello World', content: 'Welcome to learning React!'}, {id: 2, title: 'Installation', content: 'You can install React from npm.'} ]; ReactDOM.render( <Blog posts={posts} />, document.getElementById('root') );
key会作为给React的提示,但不会传递给你的组件。如果您的组件中需要使用和key
相同的值,请用其他属性名显式传递这个值:
const content = posts.map((post) => <Post key={post.id} id={post.id} title={post.title} /> );
上面例子中,Post
组件可以读出props.id
,但是不能读出props.key
表单
HTML表单元素与React中的其他DOM元素有所不同,因为表单元素生来就保留一些内部状态。例如,下面这个表单只接受一个唯一的name。
<form>
<label>
Name:
<input type="text" name="name" />
</label>
<input type="submit" value="Submit" />
</form>
当用户提交表单时,HTML的默认行为会使这个表单跳转到一个新页面。在React中亦是如此。但大多数情况下,我们都会构造一个处理提交表单并可访问用户输入表单数据的函数。实现这一点的标准方法是使用一种称为“受控组件”的技术。
受控组件
在HTML当中,像<input>
,<textarea>
, 和 <select>
这类表单元素会维持自身状态,并根据用户输入进行更新。但在React中,可变的状态通常保存在组件的状态属性中,并且只能用 setState()
方法进行更新。
我们通过使react变成一种单一数据源的状态来结合二者。React负责渲染表单的组件仍然控制用户后续输入时所发生的变化。相应的,其值由React控制的输入表单元素称为“受控组件”。
例如,我们想要使上个例子中在提交表单时输出name,我们可以写成“受控组件”的形式:
class NameForm extends React.Component { constructor(props) { super(props); this.state = {value: ''}; this.handleChange = this.handleChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } handleChange(event) { this.setState({value: event.target.value}); } handleSubmit(event) { alert('A name was submitted: ' + this.state.value); event.preventDefault(); } render() { return ( <form onSubmit={this.handleSubmit}> <label> Name: <input type="text" value={this.state.value} onChange={this.handleChange} /> </label> <input type="submit" value="Submit" /> </form> ); } }
由于 value
属性是在我们的表单元素上设置的,因此显示的值将始终为 React数据源上this.state.value
的值。由于每次按键都会触发 handleChange
来更新当前React的state,所展示的值也会随着不同用户的输入而更新。
使用”受控组件”,每个状态的改变都有一个与之相关的处理函数。这样就可以直接修改或验证用户输入。例如,我们如果想限制输入全部是大写字母,我们可以将handleChange
写为如下:
handleChange(event) { this.setState({value: event.target.value.toUpperCase()});
在React应用中,对应任何可变数据理应只有一个单一“数据源”。通常,状态都是首先添加在需要渲染数据的组件中。然后,如果另一个组件也需要这些数据,你可以将数据提升至离它们最近的共同祖先中。你应该依赖 自上而下的数据流,而不是尝试在不同组件中同步状态。
组合 vs 继承(即slot)
包含关系
一些组件不能提前知道它们的子组件是什么。这对于 Sidebar
或 Dialog
这类通用容器尤其常见。
我们建议这些组件使用 children
属性将子元素直接传递到输出。
function FancyBorder(props) { return ( <div className={'FancyBorder FancyBorder-' + props.color}> {props.children} </div> ); }
这样做还允许其他组件通过嵌套 JSX 来传递子组件。
function WelcomeDialog() { return ( <FancyBorder color="blue"> <h1 className="Dialog-title"> Welcome </h1> <p className="Dialog-message"> Thank you for visiting our spacecraft! </p> </FancyBorder> ); }
虽然不太常见,但有时你可能需要在组件中有多个入口,这种情况下你可以使用自己约定的属性而不是 children
:
function SplitPane(props) { return ( <div className="SplitPane"> <div className="SplitPane-left"> {props.left} </div> <div className="SplitPane-right"> {props.right} </div> </div> ); }
function App() { return ( <SplitPane left={ <Contacts /> } right={ <Chat /> } /> ); }
类似 <Contacts />
和 <Chat />
这样的 React 元素都是对象,所以你可以像任何其他元素一样传递它们。