一、条件渲染
在React里,我们可以创建不同的组件来封装我们需要的功能。我们也可以根据组件的状态,只渲染组件中的一部分内容,而条件渲染就是为此而准备的。在React中,我们可以像在JavaScript中写条件语句一样地写条件渲染语句,如:
function Greet(props) {
const isLogined = props.isLogined;
if (isLogined) {
return <div>Hello !</div>;
}
return <div>Please sign in</div>;
}
ReactDOM.render(
<Greet isLogined={true} />,
document.getElementById('root')
);
这将渲染出:
<div>Hello !</div>
1、使用变量来存储元素
我们也可以使用变量来存储元素,如:
function LogBtn(props) {
var button;
const isLogined = props.isLogined;
if (isLogined) {
button = <button>退出</button>
} else {
button = <button>登陆</button>
}
return <div>You can {button}</div>;
}
ReactDOM.render(
<LogBtn isLogined={false} />,
document.getElementById('root')
);
2、使用&&运算符进行渲染
由于JavaScript语法对待&&
运算符的性质,我们也可以使用&&运算符来完成条件渲染,如:
function LogBtn(props) {
var button;
const isLogined = props.isLogined;
return (
<div>Hello
{!isLogined && (
<button>请登陆</button>
)}
</div>
)
}
当props.isLogined
为false的时候,就会渲染出:
<div>Hello <button>请登录</button></div>
3、使用三目运算符进行渲染
我们可能已经发现了,其实JSX可以像一个表达式那样子灵活使用,所以,我们自然也可以使用三目运算符进行渲染,如:
function LogBtn (props) {
const isLogined = props.isLogined;
return (
<div>You can
<button>{isLogined ? '退出' : '登陆'}</button>
</div>
)
}
4、阻止整个组件的渲染
有时候,我们希望是整个组件都不渲染,而不仅仅是局部不渲染,那么这种情况下,我们就可以在render()
函数里返回一个null
,来实现我们想要的效果,如:
function LogBtn (props) {
const isLogined = props.isLogined;
const isShow = props.isShow;
if (isShow) {
return (
<div>You can
<button>{isLogined ? '退出' : '登陆'}</button>
</div>
)
}
return null;
}
注意: 组件里返回null
不会影响组件生命周期的触发,如componentWillUpdate
和componentDidUpdate
仍然会被调用
二、列表渲染与keys
在JavaScript中,我们可以使用map()
函数来对一个数组列表进行操作,如:
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(number => number*2);
console.log(doubled); // 得到[2, 4, 6, 8, 10]
同样的,在React里,我们也可以使用map()
来进行列表渲染,如:
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map(number => {
return (
<li>{number}</li>
)
});
ReactDOM.render(
<ul>{listItems}</ul>,
document.getElementById('root')
)
这将得到:
<ul><li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
当然,我们还可以进行更好的封装,如:
function NumberList (props) {
const numbers = props.numbers;
const listItems = numbers.map(number => {
return (
<li>{number}</li>
)
});
return <ul>{listItems}</ul>
}
当我们运行以上的代码的时候,会发现控制台提示:Each child in an array or iterator should have a unique "key" prop
,因此,我们需要为列表项的每一个项分配一个key
,来解决这个问题,通常而言,我们可以使用以下几种方式来提供key
:
- 使用数据项自身的ID,如
<li key={item.itemId}>
- 使用索引下标(
index
),如:
const listItems = numbers.map((number, index) => {
<li key={index}>{number}</li>
});
但是React不推荐在需要重新排序的列表里使用索引下标,因为会导致变得很慢。
注意: 只有在一个项的同胞里区分彼此的时候,才需要使用到key,key不需要全局唯一,只需要在一个数组内部区分彼此时唯一便可。key的作用是给React一个提示,而不会传递给组件。如果我们在组件内需要同样的一个值,可以换个名字传递,如:
const content = posts.map(post => (
<Post key={post.id} id={post.id} title={post.title} />
));
三、表单
表单和其他的React中的DOM元素有所不同,因为表单元素生来就是为了保存一些内部状态。在React中,表单和HTML中的表单略有不同
1、受控组件
HTML中,<input>
、<textarea>
、<select>
这类表单元素会维持自身状态,并根据用户输入进行更新。不过React中,可变的状态通常保存在组件的this.state
中,且只能用setState()
方法进行更新,如:
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('Your name is '+this.state.value);
event.preventDefault();
}
render () {
return (
<form onSubmit={this.handleSubmit}>
Name: <input type="text" value={this.state.value} onChange={this.handleChange} />
<input type="submit" value="Submit" />
</form>
)
}
}
和HTML中不同的是,React中的textarea
并不需要写成<textarea></textarea>
的形式,而是写成<textarea value="" ... />
的形式便可。而对于HTML中的select
标签,通常做法是:
<select>
<option value="A">A</option>
<option value="B" selected>B</option>
<option value="C">C</option>
</select>
但是React中,不需要在需要选中的option
处加入selected
,而只需要传入一个value,就会自动根据value来选中相应的选项,如:
<select value="C">
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
</select>
那么如上述例子,C所在的这个option
就会被选中
2、多个输入的解决办法
通常一个表单都有多个输入,如果我们为每一个输入添加处理事件,那么将会非常繁琐。好的一个解决办法是,使用name,然后根据event.target.name
来选择做什么。如:
class Form extends React.Component {
constructor (props) {
super(props);
this.state = {
name: '',
gender: '男',
attend: false,
profile: ''
};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleInputChange (event) {
const target = event.target;
const value = target.type==='checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
handleSubmit (event) {
this.setState({
profile: `姓名:${this.state.name},${this.state.gender},${this.state.attend ? '参加' : '不参加'}活动`
});
event.preventDefault();
}
render () {
return (
<form>
<p>姓名:<input name="name" value={this.state.name} onChange={this.handleInputChange} /></p>
<p>性别:
<select name="gender" value={this.state.gender} onChange={this.handleInputChange}>
<option value="男">男</option>
<option value="女">女</option>
</select>
</p>
<p>是否参加:<input name="attend" type="checkbox" onChange={this.handleInputChange} checked={this.state.attend} /></p>
<input type="submit" value="Submit" onClick={this.handleSubmit} />
<p>您的报名信息:{this.state.profile}</p>
</form>
)
}
}
3、非受控组件
大多数情况下,使用受控组件
实现表单是首选,在受控组件中,表单数据是交由React组件处理的。如果想要让表单数据由DOM处理(即数据不保存在React的状态里,而是保存在DOM中),那么可以使用非受控组件
,使用非受控组件
,可以无需为每个状态更新编写事件处理程序,使用ref
即可实现,如:
class NameForm extends React.Component {
constrcutor(props) {
super(props)
}
handleSubmit: (event) => {
console.log('A name was submitted: ', this.input.value)
event.preventDefault()
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name: <input type="text" ref={input => this.input = input} />
</label>
<input type="submit" value="submit" />
</form>
)
}
}
对于非受控组件
,如果要指定默认值,那么可以使用defaultValue
,如:
<input type="text" defaultValue="Hello" ref={input => this.input = input} />
相应的,type="checkbox"
和type="radio"
,则使用defaultChecked
四、状态提升
当需要几个组件共用状态数据的时候,可以使用状态提升技术。核心思想在于:把数据抽离到最近的共同父组件,父组件管理状态(state),然后通过属性(props)传递给子组件。如实现一个货币转换的组件,可以如下:
1、首先定义转换函数
function USD2RMB (amount) {
return amount * 6.7925;
}
function RMB2USD (amount) {
return amount * 0.1472;
}
function convert (amount, typeFn) {
return typeFn(amount);
}
2、定义组件
我们希望在RMB的输入表单上上输入的时候,USD的输入表单上的数值也同步更新,这种情况下,如果RMB组件自己管理自己的状态,是很难以实现的,因此,我们需要让这个状态提升自父组件进行管理。如下:
class CurrencyInput extends React.Component {
constructor (props) {
super(props)
this.handleChange = this.handleChange.bind(this)
}
handleChange (event) {
this.props.onInputChange(event.target.value)
}
render () {
const value = this.props.value
const type = this.props.type
return (
<p>{type}: <input type="text" value={value} onChange={this.handleChange} /></p>
);
}
}
最后定义一个共同的父组件,如下:
class CurrencyConvert extends Component {
constructor (props) {
super(props);
this.state = {
type: 'RMB',
amount: 0
}
this.handleRMBChange = this.handleRMBChange.bind(this);
this.handleUSDChange = this.handleUSDChange.bind(this);
}
handleRMBChange (amount) {
this.setState({
type: 'RMB',
amount
});
}
handleUSDChange (amount) {
this.setState({
type: 'USD',
amount
});
}
render () {
const type = this.state.type;
const amount = this.state.amount;
const RMB = type==='RMB' ? amount : convert(amount, USB2RMB);
const USD = type==='USD' ? amount : convert(amount, RMB2USB);
return (
<div>
<p>Please Input:</p>
<CurrencyInput type="RMB" value={RMB} onInputChange={this.handleRMBChange} />
<CurrencyInput type="USD" value={USD} onInputChange={this.handleUSDChange} />
</div>
);
}
}
五、组合vs继承
React推崇更多的是使用组合,而非使用继承。对于一些使用场景,React给出的建议如下:
1、包含关系
当父组件不知道子组件可能的内容是什么的时候,可以使用props.children
,如:
function Article (props) {
return (
<section>
<aside>侧边栏</aside>
<article>{props.children}</article>
</section>
);
}
function App () {
return (
<Article>这是一篇文章</Article>
);
}
这将渲染得到:
<section>
<aside>侧边栏</aside>
<article>这是一篇文章</article>
</section>
我们还可以自定义名称,因为JSX实际上会被转化为合法的JS表达式,所以,还可以有:
function Article (props) {
return (
<section>
<aside>{props.aside}</aside>
<article>{props.children}</article>
</section>
);
}
function App () {
return (
<Article aside={
<h1>这是一个侧栏</h1>
}>这是一篇文章</Article>
);
}
这将渲染得到:
<section>
<aside><h1>这是一个侧栏</h1></aside>
<article>这是一篇文章</article>
</section>
2、何时使用继承?
在Facebook的网站上,使用了数以千计的组件,但是实践证明还没有发现需要使用继承才能解决的情况。
属性和组合为我们提供了清晰的、安全的方式来自定义组件的样式和行为,组件可以接受任意元素,包括:基本数据类型、React元素、函数。
如果要在组件之间复用UI无关的功能,那么应该将其提取到单独的JavaScript模块中,这样子可以在不对组件进行扩展的前提下导入并使用函数、对象、类