React 入门教程之七 -- List 和 Form
list 列表和 key
在 JavaScript 中我们通常使用 map method 来对一个 list 的每个元素进行操作:
const numbers = [1, 2, 3, 4, 5];
const double = numbers.map((number) => { return number * 2});
console.log(double)
//output:
//[ 2, 4, 6, 8, 10 ]
在 React 中对一个 list 的元素进行操作方法类似。
我们可以在 JSX 中通过大括号{}
来建立一个 elements 的集合,下面示例中我们将 map 的返回定义为 <li>
元素并赋值给 listItems:
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) => <li>{number}</li>)
ReactDOM.render(
<ul>{listItems}</ul>,
document.getElementById('root')
);
注意在 render 中我们将 listItems 放在 <ul>
元素中。
通常情况下我们将 lists 放在一个 component 中:
const NumberList = (props) => {
const numbers = props.numbers;
const listItems = numbers.map((number) => <li>{number}</li>)
return (
<ul>{listItems}</ul>
)
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers}/>,
document.getElementById('root')
);
当运行以上代码时,在浏览器终端会有一个 warning 警告信息:Each child in a list should have a unique "key" prop.:
Key 是一个特殊的 string 字符串属性需要给创建的 list element 添加的。它可以用来定位 list 中的每个元素。
下面我们给 list item 添加 Key 字符串属性:
const NumberList = (props) => {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>);
return (
<ul>{listItems}</ul>
);
}
添加后报警就会消除。
Keys
Key 可以帮助 React 识别哪个 item 修改过,被删除,被添加。以上示例中,我们在 map 中创建 item 时给其 key 属性,这样每个 item 可以有确切的属性值。
每个 list item 最好设置一个特殊的标识 key string 来区别于其他 items。最常用的就是使用数据中的 ID 作为 key:
const TodoItems = (props) => {
const todos = props.todos;
const listItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
)
return (
<ul>{listItems}</ul>
);
}
const todos = [
{id: 1, text: '123'},
{id: 2, text: '456'}
];
ReactDOM.render(
<TodoItems todos={todos} />,
document.getElementById('root')
);
当没有特定的 ID 来作为标识时,作为最后的选择,可以使用 item 的 index 作为 key:
const TodoItems = (props) => {
const todos = props.todos;
const listItems = todos.map((todo, index) =>
<li key={index}>
{todo.text}
</li>
)
return (
<ul>{listItems}</ul>
);
}
如果 items 的顺序可能会发生变化的话,不推荐使用 index 作为 key 使用,因为可能对性能产生影响并且对 component 的 state 造成问题。如果没有定义确切的 key 给 items,React 默认会使用 index 作为 keys。
拆解 component 时 key 的处理
keys 是对应与一个数组的内容而言的,它并不能单独存在。例如我们要拆解上面的 NumberList,提取出 ListItem,则需要将 key 定义在 <ListItem />
元素中而不是 ListItem component 内部的 <li>
中:
const ListItem = (props) => {
return (
<li>{props.value}</li>
);
}
const NumberList = (props) => {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<ListItem key={number.toString()} value={number}/>);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
如果写成下面模式就是错误的:
function ListItem(props) {
const value = props.value;
return (
<li key={value.toString()}>
{value}
</li>
);
}
每个 item 的 key 必须是特定的
数组中每个 items 使用的 key 必须是互相独立且不相同的,但并不需要在全局下互相独立。在两个单独的数组中可以,其元素可以使用相同的 key:
const React = require('react')
const ReactDOM = require('react-dom')
const 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')
);
上面示例中,我们在 Blog component 中定义了两个 JSX,都创建了 list elements,每个元素的 key 使用了对应的 id 属性。在每个 list 内部 key 是互相独立的。可以看到不只是 <li>
元素可以加 key,只要通过 map 定义了一个 array 数组,就可以给每个元素加上 key 属性来互相独立识别。
key 是为了给 React 识别用的。它本身并不作为一个普通 prop 传给 components,也就是在 component 内部并不能使用这个 key 数据,如果想要在 component 中使用这个数据则需要单独定义一个其他 prop 来传入 key 数据:
const Post = (props) => {
return (
<li>
{props.id}: {props.title}
</li>
)
}
const Blog = (props) => {
const sideBar = (
<ul>
{props.posts.map((post) =>
<Post key={post.id} id={post.id} title={post.title} />
)}
</ul>
);
...
...
...
}
上面示例中,Post component 无法直接访问 key 的数据,所以我们在调用 Post 时单独定义一个 id 属性并赋值为 key 相同的数据,这样就间接的可以在 Post component 中通过 id 来读取 key 的数据。
在之前的 ListItem 示例中,我们声明了一个单独的 listItems 变量并在后续返回中将其放在 <ul>
中:
const NumberList = (props) => {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<ListItem key={number.toString()} value={number}/>);
return (
<ul>{listItems}</ul>
);
}
JSX 支持嵌入任何的 JavaScript 表达式,只需要使用大括号包围即可,所以上面的代码可以修改为以下模式:
return (
<ul>
{numbers.map((number) =>
<ListItem key={number.toString()} value={number} />);}
</ul>
);
使用哪种方式来定义 JSX 取决于对应的使用场景,总的原则是要方便与代码阅读,逻辑清晰。需要注意的是如果 map()
method 中层级太复杂,可以考虑将其拆分为多个 components。
Forms 表格
HTML 的 form element 和其他 DOM elements 有点区别,因为 form element 包含有一些内部 state 数据,例如下面的 html 示例包含一个 from 表格:
<form>
<label>
Name:
<input type="text" name="name">
</label>
<input type="submit" value="Submit">
</form>
以上示例中的 form 表格会有一个默认的 behavior 动作,那就是当用户点击 submit 按钮时会打开一个新页面。如果你不需要这个默认行为,同时需要提取 input 的信息时,需要在 submit event 事件发生时对其使用 preventDefault()
method,标准的实现方法是通过 controlled components 可控构件 来处理。
controlled components
在 html 中,form 的元素如:<input>
, <textarea>
, 和 <select>
都有他们自己的 state 且随着用户输入信息而自动更新。在 react 中,可变的 state 存储在 component 的 state property 中且只能通过 setState()
更新。
我们可以将 from 元素的 state 和 component 的 state 合并起来作为唯一的数据来源,这样 component 既可以渲染 form 也可以控制 form 中的输入信息。一个 input 输入信息受 react component 控制的 form element 叫做 controlled component。
如下示例中,我们构建一个 controlled component 来记录用户 input 的内容:
const React = require('react')
const ReactDOM = require('react-dom')
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 has been submited: ${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>
);
}
}
ReactDOM.render(
<NameForm />,
document.getElementById('root')
);
我们将 input 的 value 属性定义为 this.state.value
的值,这里显示的永远是当前 state value 的值。当 handleChange 被触发时,会将当前用户输入的内容更新到 state value 中,然后触发 render 更新 form。
通过 controlled component 可以使 form 中 input 的内容受控于 state,这样我们就可以操作输入的数据用于其他任何地方了。
textarea 标签
在 html 中我们使用 textarea 来定义一段文本区域:
<textarea>
Hello there, this is some text in a text area
</textarea>
在 react 中,类似于上面的 input 标签,我们将文本内容放在 value 属性中,如下示例:
const React = require('react')
const ReactDOM = require('react-dom')
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: 'please write some words to discribe yourself'};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
};
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert(`a discribe has been submited: ${this.state.value}`);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
TextArea:
<textarea type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
ReactDOM.render(
<NameForm />,
document.getElementById('root')
);
注意我们在构造器中给他 state value 定义了初始值,这样在第一次访问页面时就会有一段默认文字了。
select 标签
在 html 中 select 标签可以创建一个下拉菜单控件:
<select>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option selected value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
注意上面的示例中,Coconut 选项会默认选中,因为其定义了 selected 属性。在 react 中我们可以在 select 根标签中直接定义 value 属性来定义当前选中的是哪一个 option。这在 controlled component 中可以很方便的管理及更新 select element 的 value:
const React = require('react')
const ReactDOM = require('react-dom')
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: 'sports'};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
};
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert(`you favorite is: ${this.state.value}`);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
your favorite:
<select value={this.state.value} onChange={this.handleChange}>
<option value="sleep">Sleep</option>
<option value="sports">Sports</option>
<option value="takePhoto">Take Photo</option>
<option value="work">Work</option>
</select>
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
ReactDOM.render(
<NameForm />,
document.getElementById('root')
);
以上示例中,我们将 state 的 value 赋值给 select 的 value 这样 select 当前选中项总是 state 中的值,在 handleChange 触发时会更新 state 中的 value 并 render 页面。
注意我们可以给 value 赋值一个数组,这样就可以同时选中多个 option:
<select multiple={true} value={['B', 'C']}>
以上几种 form 控件起始基本结构都类似,他们都核心概念就是将元素的 value 属性和 state 挂钩,从而使 controlled component 生效。
处理多个 input 输入源
当我们需要在 component 中同时处理多个 input 元素时,可以给每个 input 添加 name 属性,然后再对应的 handle function 中通过 event.target.name 来区分他们:
const React = require('react')
const ReactDOM = require('react-dom')
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {
isGoing: true,
numberOfGuests: 2
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
};
handleChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({[name]: value});
}
handleSubmit(event) {
alert(`Is Going: ${this.state.isGoing}, Number Of Guests: ${this.state.numberOfGuests}`);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Is Going:
<input
name="isGoing"
type="checkbox"
checked={this.state.isGoing}
onChange={this.handleChange} />
</label>
<br />
<label>
Number of Guests:
<input
name="numberOfGuests"
type="number"
value={this.state.numberOfGuests}
onChange={this.handleChange} />
</label>
<br />
<input type="submit" value="Submit" />
</form>
);
}
}
ReactDOM.render(
<NameForm />,
document.getElementById('root')
);
以上示例中我们使用了 ES6 中新加入的特性: this.setState({[name]: value});
再 object 中使用方括号 []
来调用变量。setState()
会自动合并更新到 state 中。
input Null value
我们可以给一个 input 元素定义初值,默认情况下在页面加载完成后,input 框就可以立刻被用户进行编辑,有时候我们不希望再一开始就让用户修改 input 中的数据,此时可以临时给 input 的 value 属性赋值为 null 或 undefined 就可以了:
const React = require('react')
const ReactDOM = require('react-dom')
ReactDOM.render(
<input value="hi" />,
document.getElementById('root')
);
setTimeout(() => {
ReactDOM.render(
<input value={null} />,
document.getElementById('root')
);
}, 2000);
以上示例中,在页面刚加载的前 2 秒,用户无法修改默认的 hi
字符串。
标签:无