Translation
四、Forms
在React中,HTML表单元素与其他DOM元素所起的作用有所不同。因为form表单元素自动地保持一些内部状
态。比如,在如下例子中,表单元素接受一个name:
<form>
<label>
<input type="text" name="name" />
</label>
<input type="submit" value="Submit" />
</form>
当用户点击提交按钮时上面表单执行默认的HTML表单行为---打开一个新页面。如果你想在React中实现这
种行为,他就是这样实现的。 但是在很多情况下,使用JavaScript方法处理表单提交和有权限去处理用户
输入的数据是非常方便的。实现的标准方式是使用一个被称为'控制组件(controlled components)'的
技术。
1.1 控制组件(controlled components)
在HTML中,像<input>,<textarea>,和<select>等典型的表单元素都保持了他们自己的状态并且在更新
表单元素的时候是根据用户输入情况进行更新的。在React中,易变的状态通常保持在组建的状态属性中,而
且只能被'setState()'方法更新。
我们可以通过React来合并以上两种情况。这样渲染表单元素的React组件同时也能够根据后来用户的输入
情况来进行控制 。一个被React以这样的方式而控制的表单元素被称为'控制组件'。
比如,在上一个例子中,当点击提交按钮时我们想要表单元素记录用户输入的值,可以用控制组件按如下方式
来写:
class NameForm extends Reac.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>
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
因为值的属性被设置在表单元素中,值总是通过'this.state.value'来进行展示,进而更新React状态。
因为handleChange作用于每一个按键从而去更新React状态,所以被显示的值将按用户类型进行更新。
有一个可控制的组件,每个状态将有一个相关的处理函数。这样就使得更改或者校验用户输入变得简单。
比如我们一个names,这个names被要求所有字母大写,我们应该按如下格式来写:
handleChange(event){
this.setState({value:event.target.value.toUpperCase()});
}
1.2 textarea标签
在HTML中,textarea标签由标签内的内容来定义他的文本。
<textarea>
Hello there,this is some text in a text area
</textarea>
在React中一个<textarea>用一个'value'属性代替。这样的话,<textarea>标签可以写成与单行输入框
相似的form表单输入框:
class EssayForm extends React.Component {
constructor(props){
super(props);
this.state={
value: 'Please write an essay about your favorite DOM element.'
};
this.handleChange=this.handleChange.bind(this);
this.handleSubmit=this.handleSubmit.bind(this);
}
handleChange(event){
this.setState({value:event.target.value});
}
handleSubmit(event){
alert('An essay was submitted :'+this.state.value);
event.preventDefault();
}
render(){
return (
<form onSubmit={this.handleSubmit}>
<label>
<textarea type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
注意到构造器中初始化了'this.state.value',所以文本域中一开始就会有内容。
1.3 select标签
在HTML中,<select>标签会创建一个下拉列表。如下:
<select>
<options value="grapefruit">Grapefruit</options>
<options value="lime">Lime</options>
<options selected value="coconut">Coconut</options>
<options value="mango">Mango</options>
</select>
上面代码中,由于'selected'属性默认显示'Coconut',而在React中这种方式会被在根标签'select'
中使用一个值属性替代。这样的话你仅仅需要在一个地方更新它,所以更方便。如下:
class FlavorForm extends React.Component {
constructor(props){
super(props);
this.state={value:'coconut'};
this.handleChange=this.handleChange.bind(this);
this.handleSubmit=this.handleSubmit.bind(this);
}
handleChange(event){
this.setState({value:event.target.value});
}
handleSubmit(event){
alert('Your favorite flavor is: ' + this.state.value);
event.preventDefault();
}
render(){
return(
<form onSubmit={this.handleSubmit}>
<label>
Pick your favorite La Croix flavor:
<select value={this.state.value} onChange={this.handleChange}>
<options value="grapefruit">Grapefruit</options>
<options value="lime">Lime</options>
<options selected value="coconut">Coconut</options>
<options value="mango">Mango</options>
</select>
</label>
</form>
);
}
}
综上,<input type="text">, <textarea>, 与 <select>三个标签工作原理相似---都接受一个你
可以去实施一个控制组件的值属性。
1.4 处理多个输入框:
当你需要去处理多个被控制的输入框的元素时,你可以在每个元素中增加一个'name'属性,基于
'event.target.name'并让处理函数选择去做。
如下实例:
class Reservation extends React.Component {
constructor(props) {
super(props);
this.state={
isGoing:true,
numberOfGuests:2
};
this.handleInputChange=this.handleInputChange.bing(this);
}
handleInputChange(event){
const target=event.target;
const value=target.type==='checkbox' ? target.checked : target.value;
const name=target.name;
this.setState({
[name]:value
});
}
}
要知道我们用ES6属性名语法去更新状态键:
this.setState({
[name]:value
});
其作用等效下面ES5代码:
var partialState={};
partialState[name]=value;
this.setState(partialState);
因为'setState()'自动融合了部分状态到当前状态,所以我们仅仅需要去调用改变的部分。
1.5 控制组件的替代品
有时候使用控制组件会显得比较冗余,因为你需要针对每一个可改变的数据的方法写一个事件处理程序并且
将所有的输入状态传输到一个React组件。而且当你准备把一个先前存在的代码库转换成React或将一个非
React库整合成一个React应用时会令你非常恼火 。在这种情况下,你可能会考虑实现一个表单输入框的替
代品---非受控组件,来替换受控组件。
三、列表和关键字
首先,回忆一下在JavaScript中是如何将数组转换为lists。
代码如下,我们用map()函数来将一个数组的值变为原来的2倍并赋给一个常量:
const numbers=[1,2,3,4,5];
const doubled=numbers.map((number)=>number*2);
console.log(doubled);
结果输出: [2,4,6,8,10]
在React中,将数组转换成lists元素是一样的。
1.1渲染多个组件
你可以创建元素集合并用‘{}’将其包含在JSX中。
接下来我们用map()函数来循环数组元素,每次循环返回一个<li>元素。最后将返回的元素
分配给常量listItems:
const numbers=[1,2,3,4,5];
const listItems=number.map((number)=>
<li>{number}</li>
);
我们将整个listItems数组包含在一个<ul>标签元素中,并将其渲染至DOM上:
ReactDOM.render(
<ul>{listItems}</ul>
document.getElementById('root');
);
1.2基本List组件
通常你将渲染一个lists内部组件。
我们将之前的例子重构重构为一个组件,使得该组件接受一个数组组成的数组并将结果无序输出。
function NUmmberList(props){
const numbers=props.numbers;
const listItems=number.map((number)=>
<li>{number}</li>
);
return (
<ul>{listItems}</ul>
);
}
const numbers=[1,2,3,4,5];
ReactDOM.render(
<NumberList number={number} />,
document.getElementById('root')
);
当你运行这段代码时,将会看到一条警告:应该为'list items'提供一个'key'。 ‘key’是一个特殊的
字符串属性,在创建lists时创建。下一个章节我们将讨论它如何引入。
让我们在number.map()中为lists元素分配一个'key'来解决缺少'key'的问题。
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 numebrs={numbers} />,
document.getElementById('root')
);
1.3 Keys
Keys帮助React确定哪个元素已经被改变,被添加,或被删除。 'Keys'应该被赋值给数组元素来唯一标识
该数组元素。
const numbers=[1,2,3,4,5];
const listItems=numbers.map((number)=>
<li key={number.toString()}
{number}
</li>
);
挑选'key'最好是不同于其他兄弟节点的、独一无二的的字符串属性,大部分时候使用某个时间作为ID来唯一
标识数组元素:
const todoItems=todos.map((todo)=>
<li key={todo.id}>
<todo.text}
</li>
);
当你没有一个稳定的IDs时,可以用元素的索引作为'key':
const todoItems=todos.map((todo,index)=>
//Only do this if items have no stable IDs
<li key={index}>
{todo.text}
</li>
);
如果元素能重新排序我们不建议使用索引作为元素的‘key’,因为那样做效率低下。
如果你感兴趣可以去深入了解为什么'keys'是必要的。
1.4 根据'Keys'提取组件
'Keys'仅仅在周围数组的上下文中才有意义。
比如,如果你提取一个ListItem组件,你应该保持'key'在数组'<ListItem />'元素中,而不是在
'ListItem'自身的根节点列表<li>元素中。
案例:错误示例
function ListItem(props){
const value=props.value;
return (
//Wrong! There is no need to specify the key here:
<li key={value.toString()}>
{value}
</li>
);
}
function NumberList(props){
const numbers=props.numbers;
const listItems=numbers.map((numebr)=>
//Wrong! The key should hanve been specified here:
<ListItem value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
const numbers=[1,2,3,4,5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
案例:正确示例
function ListItem(props){
//Correct! There is no need to specify the key here:
return <li>{props.value}</li>
}
function NumberList(props){
const numbers=props.numbers;
const listItems=numbers.map((numebr)=>
//Correct! The key should have been specified here:
<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')
);
一个正确的方式是在'map()'函数中配置'Keys'
1.5 'Keys'必须唯一
用在数组中的'Keys'必须在其他兄弟节点中是唯一的 ,但并非全局唯一,在两个不同的数组中我们可以使用
相同的'Keys' :
function Blog(props){
const sidebar=(
{props.posts.map((post)=>
<li key={post.id}>
{post.title}
</li>
)}
);
const content =props.posts.map((post)=>
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.content}</p>
</div>
);
return (
<div>
<hr />
{content}
</div>
);
}
const posts=[
{id:1,title:'Hello World',content:'Welcome to learning React!'},
{id:2,title:'Installtion',content:'You can install React from npm!'}
];
ReactDOM.render(
<Blog posts={posts} />,
document.geElementById('root')
);
'Keys'在React中作为一个提示,但是他们没有在你的组件中获得通过。如果在你的组件中需要相同的值,
使用一个不同的名字明确地把它作为一个属性:
const content =posts.map((post)=>
<Post
key={post.id}
id={post.id}
title={post.title}
/>
);
上面的例子中,'Post'组件能读取到'props.id'但不能读取到'props.key'。
1.6 在JSX中嵌入map()函数
在下面的实例中,声明一个单独的listItems变量并将其放在JSX中:
function NumberList(props){
const number=props.numbers;
const listItems=number.map((number)=>
<ListItem key={number.toString()}
value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
JSX允许在'{}'中嵌入任意表达式,所以我们可以将map()函数直接放在需要的地方:
function NumberList(props){
const numbers=props.number;
return (
<ul>
{numbers.map((number)=>
<ListItem key={number.toString()}
value={number} />
)}
</ul>
);
}
有时候这种方式会使代码更清晰,但也很容易被被滥用。 像在JavaScript中,为了程序的可读性,由你决
定是否值得去提取一个变量。 要知道如果map()体被嵌套的太多,这时候就该考虑提取一个组件了。
二、Conditional Rendering(条件渲染)
在React中,你可以根据自己的需要创建封装不同行为的组件,然后你可以根据应用的状态渲染部分组件。
条件渲染在React中的工作方式与在JavaScript中的工作方式相同,使用像 ‘if’或条件运算符的JavaScript
操作去创建的元素代该元素当前状态,然后让React更新这个UI去匹配这个元素。
eg.下面是两个组件:
function UserGreeting(props) {
return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
return <h1>Please sign up.</h1>;
}
在这里创建一个Greeting组件,该组件根据用户的操作而显示其中一个组件。
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
ReactDOM.render(
// Try changing to isLoggedIn={true}:
<Greeting isLoggedIn={false} />,
document.getElementById('root')
);
以上实例根据isLoggedIn属性的值渲染不同的问候内容。
1、变量
变量可以存储元素,当组件的其他输出部分不能改变时使用变量可以有条件的渲染部分组件。
使用如下分别代表注销(Logout)和登录(Login)按钮的组件进行说明:
function LoginButton(props){
return (
<button onClick={props.onClick}>
Login
</button>
);
}
function LogoutButton(props){
return (
<button onClick={props.onClick}>
Logout
</button>
);
}
接下来我们将创建一个有状态的组件:LoginControl , 该组件将根据当前状态渲染登录或者注销按钮,
也会根据之前的例子渲染一个<Greeting/> :
class LoginControl extends React.Component{
constructor(props){
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state={isLoggedIn: false};
}
handleLoginClick(){
this.setState({ isLoggedIn: true });
}
handleLogoutClick(){
this.setState({ isLoggedIn: false });
}
render(){
const isLoggedIn = this.state.isLoggedIn;
let button = null;
if(isLoggedIn){
button = <LogoutButton onClick = { this.handleLogoutClick } />;
}else{
button = <LoginButton onClick = { this.handleLoginClick } />;
}
return (
<div>
<Greeting isLoggedIn={isLoggedIn} />
{button}
</div>
);
}
}
ReactDOM.render(
<LoginControl />,
document.getElementById('root')
);
声明一个变量并且使用'if'语句去有条件的渲染一个组件是一个很好的方式,因为有时候你可能只是想用
一个较短的语法。
在JSX中有一些内联条件方法如下:
1.1 内联'if'语句的逻辑操作:
你可能会在JSX中通过大括号嵌入认可表达式,包括JavaScript逻辑操作,这对于有条件的嵌入一个元素
非常方便:
function Mailbox(props){
const unreadMessages = props.unreadMessages;
return (
<div>
<h1>hello!<h1/>
{unreadMessages.length > 0 &&
<h2>
You have { unreadMessages.length } unreadMessages.
</h2>
}
</div>
);
}
const messages = [ 'React','Re:React','Re:Re: React' ];
ReactDOM.render(
<Mailbox unreadMessages = { messages } />,
document.getElementById('root')
);
因为在JavaScript中工作,所以‘true&&expression’ 结果总是expression,‘false&&expression’
结果总是false 。 因此,如果条件为真,在'&&'之后的元素内容讲出现在输出框,如果为假,React将
忽略跳过它。
1.2内联'if-else'的条件操作:
有条件的内联元素渲染的另一个方法是使用JavaScript的条件运算符: 'conditon ? true :false'。
在接下来的例子中我们用这个条件操作去渲染部分文本内容:
render() {
const isLoggedIn=this.state.isLoggedIn;
return (
<div>
The user is <b>{isLoggedIn ? 'currently' : 'not'}</b>logged in
</div>
);
}
条件运算符在长表达式中很少见但也能用在其中。
render(){
const isLoggedIn = this.state.isLoggedIn;
return ({
<div>
{
isLoggedIn ?(
<LogoutButton onClick={this.handleLogoutClick} />
):(
<LoginButton onClick={this.handleLoginClick} />
)}
</div>
});
}
就像在JavaScript中,考虑到为了让你和你的团队更容易理解,需要去选择一个合适的样式。同时,要记
住无论何时,当条件变得更复杂时,也是时候考虑提取出一个组件的时候。
1.3阻止组件被渲染
有时候,即使一个组件被另一个组件渲染,你也可能想要这个组件隐藏它自己 。可以考虑返回null来代替
被渲染输出。
下面的例子中,根据属性调用warn返回的值来对组件<WarningBanner />进行渲染。如果该属性返回的
值是false,该组件不被渲染:
function Warning (props){
if(!props.warn) {
return null;
}
return (
<div>
Warning!
</div>
);
}
class Page extends React.Component{
constructor(props){
super(props);
this.state={showWarning:true}
this.handleToggleClick=this.handleToggleClick.bind(this);
}
handleToggleClick(){
this.setState(preState=>({
showWaring:!preState.showWarning
}));
}
render(){
return(
<div>
<WarningBanner warn={this.state.showWarning} />
<button onClick=={this.handleToggleClick} >
{this.state.showWarning ? 'Hide':‘Show’}
</button>
</div>
);
}
}
ReactDOM.render(
<Page />,
document.getElementById('root');
);
根据render方法返回null并不影响该组件的生命周期方法。比如,componentWillUpdate和
componentDidUpdate仍将被执行。
一、事件处理:
React元素的事件处理与DOM元素的事件处理机制类似。
两者有如下语法上的不同:
1>React事件采用驼峰命名方式命名,而不是全部小写的字符串;
2>通过JSX可以使用一个函数作为React元素的触发事件,而不是一个字符串。
比如,在HTML中:
<button onclick="activateLasers()">
Activate Lasers
</button>
在React中:
<button onClick={activateLasers}>
Activate Lasers
</button>
另一个不同之处就是你不能通过返回false来阻止React中的默认行为,必须明确地调用preventDefault。
比如,运用简单的HTML去阻止一个默认的链接打开一个新页面,可以写成如下形式:
<a href="#" onclick="console.log('The link was clicked.'); return false">
Click me
</a>
在React中却可以写成如下形式:
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
上述函数中的参数e是一个合成事件。React根据W3Cspec定义这些合成事件,所以你不用担心跨浏览器
的兼容性问题。
一般情况下,当一个DOM元素已经被创建就没有必要为DOM元素添加事件监听方法,取而代之的是在这个
元素被初始化时提供一个监听器。
当你使用ES6 的类定义一个组件时,通常为一个事件机制提供一个单独的方法。 比如,一个Toggle
组件提供一个按钮,这个按钮可以实现开关的切换:
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
你必须注意“this”关键字在JSX回调中的意义。 在JavaScript中,类的方法默认没有被绑定。如果
你忘了去绑定this.handleClick并且使用它作为触发响应事件,当函数已经被调用这将无效。
这不是React特有的行为,而是函数在JavaScript中的一部分。通常来讲,当一个方法名后没有括号
(eg. onClick={this.handleClick}) , 必须绑定这个方法。
如果认为绑定干扰了你,有两个方法可以避免; 如果你正在使用实验属性初始化语法,你可以使用属性
初始化去纠正绑定回调:
class LoggingButton extends React.Component {
// This syntax ensures `this` is bound within handleClick.
// Warning: this is *experimental* syntax.
handleClick = () => {
console.log('this is:', this);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
当然你也可以在属性初始化语法中使用箭头函数:
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
// This syntax ensures `this` is bound within handleClick
return (
<button onClick={(e) => this.handleClick(e)}>
Click me
</button>
);
}
}
这种方法回调在组件每次被renders时都会被创建。在大多数案例中,这种做法没什么问题,但是,如果
这种回调被当做一个属性传向下一个组件,这些组件可能会做一些额外的操作。 我们通常建议在构造器
中绑定或者使用一个属性初始化语法,去避免这种性能问题。