$ 前言
最近在考虑框架转型,鉴于作为一名JSer,要时时刻刻保持对新技术和流行技术的敏感性,而 React、Vue、Angular 已基本占领现前端市场,React作为领头大哥,建议年轻的JSer们都要学习使用或至少了解这门技术。
$ 版本声明
本文使用版本 React v16.2.0
$ 什么是 React ?
React是一个声明式的,高效的,并且灵活的用于构建用户界面的 JavaScript 库
一个最简单的React例子
ReactDom.render(
<h1>Hello World</h1>,
document.getElementById('root')
)
ReactDom.render
接受两个参数,第一个是要被插入的内容,第二个是插入到DOM或者说index.html
的位置
$ 一个与Html对比的简单组件
如下是一个 React 组件
class ShoppingList extends React.Componnet {
render() {
return (
<div className="shopping-list">
<h1>Shoping List for {this.props.name}</h1>
<ul>
<li>Instagram</li>
<li>WhatApp</li>
<li>Oculus</li>
</ul>
</div>
)
}
}
// Example usage: <ShoppingList name="Mark" />
在这里,ShoppingList是一个 React组件类,或 React组件类型。组件接受参数,称为属性 props
, 并通过 render
方法返回一个现实的视图层次结构。
render
方法返回您要渲染的内容描述,然后React接受该描述并将其渲染到屏幕上,特别是,render
返回一个React 元素,这是一个渲染内容的轻量级的描述。大多数
React 开发人员使用 JSX
语法,也是上述案例写到的语法。
JSX
语法的转换规则为: <div />
语法在构建是被转换为 React.createElement('div')
。因此,上面的例子等价于:
return React.createElement('div', {className: 'shopping-list'},
React.createElement('h1', /* h1 children ... */),
React.createElement('ul', /* ul children ... */)
);
既然 JSX
在 React 开发者中这么流行,那 JSX
又是什么呢?
$ JSX 语法
JSX
它是 Javascript
的一种拓展语法,能够让你的 Javascript
中和正常描述 HTML一样编写 HTML。
你可以用 花括号
将任意 Javascript
表达式嵌入到 JSX
中。例如:表达式 1 + 2
, 变量 user.firstName
, 和函数 formatName(User)
等都可以嵌入使用
function formatName(user) {
return user.firstName + ' ' + user.lastName;
}
const user = {
firstName: 'harper',
lastName: 'Perez'
}
const element = {
<h1> Hello, {formatName(user)}! </h1>
}
ReactDOM.render (
element,
document.getElementById('root')
)
请注意,为了方便阅读开发者们常将 JSX
分割成多行包裹起来,因为这可以避免分号自动插入的陷阱,如
{ 1
2 } 3
// is transformed to
{ 1
;2 ;} 3;
JSX 也是一个表达式
编译之后, JSX
表达式也就成了一个常规的 javascript
对象
也正因为如此,我们可以在 if
语句或这是 for
循环语句中使用 JSX
,用它给变量赋值,当做参数接受,或者作为函数的返回值
function getGreeting(user) {
if (user) {
return <h1>Hello. {formatName(User}</h1>;
}
return <h1>Hello, Stranger</h1>
}
用 JSX 指定属性值
你可以用花括号嵌入一个 JavaScript 表达式作为属性值
// 用引号形式
const element = <div tableIndex="0"></div>;
// 用表达式,并且表达式用花括号包裹
const element = <img src={user.avatarUrl}></img>;
用 JSX 指定子元素
如果是空标签,可以直接用 />
闭合
const element = <img src={user.avatarUrl} />
如果包含子标签:
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
比起
HTML
,JSX
更接近于Javascript
,所以React DOM
规范使用驼峰(camelCase)属性命名约定,而不是HTML属性名称,当然,html的部分属性名称也作为保留字,不可使用,例如class
;
因此,class
在 JSX 中 变为className
,tableindex
变为tableIndex
。
用 JSX 防止注入攻击
在JSX 中嵌入用户输入是安全的:
const title = response.potentiallyMaliciousInput;
// 这样是安全的
const element = <h1>{title}</h1>
默认情况下, 在渲染之前, React DOM 会格式化(escapes) JSX中的所有值. 从而保证用户无法注入任何应用之外的代码. 在被渲染之前,所有的数据都被转义成为了字符串处理。 以避免 XSS(跨站脚本) 攻击。
$ 元素渲染到DOM
正常情况下,你的 index.html
文件下会有这么一个div
<div id='root'></div>
这个root
DOM 节点挂在所有React DOM的位置。正常情况下,对于一个React单页面应用构建,只需要一个单独的根DOM节点即可。但如果要把React整合到现有的APP中,则可能会使用到多个DOM节点。
React利用render
方法将React元素渲染到DOM上,一旦元素被渲染到页面了之后,就不能在修改器子元素或任何元素的属性,就像电影里的一帧,在某以特定的时间点的UI效果,那元素的更新呢?没错,就是重新 render
function tick() {
cosnt element = {
<div>
<h1>Hello, world</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
};
ReactDom.render (
element,
document.getElementById('root')
)
}
setInterval(tick, 1000);
实际上,大多数 React 应用只会调用一次ReactDom.render(),而实现组件更新的办法就是将代码封装在有状态的组件中。
React 只更新必须更新的部分
这正是 React 的强大之处。React DOM 会将元素及其子元素与之前版本逐一对比,并只对有必要更新的 DOM 进行更新, 以达到 DOM 所需的状态。
开发过程中,更应该每个时间点UI的表现, 而不是关注随着时间不断更新UI的状态, 可以减少很多奇怪的 bug
$ 组件和属性
组件 components
和属性 props
,其中,属性是单词 property
的代码简写。
定义组件的两种办法
定义组件有两种方式
- 函数式组件定义
-
类组件定义
最简单的定义组件的方法就是写一个Javascript
函数
function Welcome(props) {
return <h1>Hello, props.name</h1>
}
这就是一个有效的组件,它接首了一个 props
参数,并返回了一个React元素,这是一个函数式组件,表面上看,他就是一个 Javascript
函数。
类组件的定义则依赖ES6 的 class
来定义,下面这种定义方法和上方是等效的;
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
渲染一个组件
// DOM标签作为组件
const element = <div />;
// React 元素作为组件
const element = <Welcome name="Sara" />;
当React 遇到一个代表用户定义组件的元素时,它将 JSX
属性以一个单独对象即
props对象
的形式传递给相应的组件,例如
function Welcome(props) {
return <h1>Hello, {props.mname] </h1>;
}
const element = <Wlecome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
)
【理解】
- 调用
ReactDOM.render()
方法并向其传入了<Welcome name="Sara" />
元素 - Raect 调用
Welcome
组件,并向其传入了{name: ‘Sara’}
作为props对象
-
Welcome
组件返回<h1>Hello, Sara</h1>
- React DOM 迅速更新 DOM,使其显示为
<h1>Hello, Sara</h1>
组件名称总是以大写字母开始, 如本例子中
<Welcome />
, 而不是<welcome />
构成组件
既然组件是单独的一个React元素,那他能单独工作,因此我们能在一个React 元素中多次引用到相同的组件, 举个例子:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>
}
function App() {
return (
<Welcome name="Sara" />
<Welcome name="Lucy" />
<Welcome name="Edite" />
)
}
ReactDOM.render(
<App />,
document.getElementBuId('root')
)
通常情况下, React apps 都有一个单独的顶层的 App
组件。如果是在已有的应用中整合React,也需要由下至上的从小的组件开始逐步整合到视图顶层的组件中。
组件必须返回一个单独的根元素,这就是为什么我们要添加一个
<div>
来包裹所有的<Welcome />
元素的原因
提取组件
对于一个React 元素,如果其中含有可复用或可能会重复使用的内容,不要害怕把它单拿出来多个更小的组件。
提取组件可能看起来是一个繁琐的工作,但是在大型的 App
中可以回报给我们的是大量的可复用组件。一个好的经验准则是如果你 UI 的一部分需要用多次 (Button
,Panel
,Avatar
),或者本身足够复杂(App
,FeedStory
,Comment
),最好的做法是使其成为可复用组件。
Props 是只读的
无论你用函数或类的方法来声明组件,
虽然 React 很灵活,但是它有一条严格的规则:**所有 React 组件都必须是纯函数,并禁止修改其自身 props **。所谓的纯函数就是:传入函数参数不会在函数执行过程中发生改变,比如自增操作 a++
。
如果props
是只读的,那传递给子元素(子组件)的参数岂不是不能修改了?那子元素如何与父元素做交互呢?React还给我们提供了状态属性 state
供我们在子组件内部修改值
状态和生命周期
状态state
, 生命周期 liftcircle
.
之前说过,一旦元素被渲染了之后就不可改变了,但我们可以通过重新渲染的方法使页面得以刷新,同样我们提到过最常用的方法是编写一个可复用的具有状态的组件,这里的状态,就是我们将要说的 state
我们对上述提过的计时器tick
中的计时功能封装成一个函数式组件如下:
function Clock(props) {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {props.date.toLocaleTimeString()}.</h2>
</div>
)
}
然后把他当做一个元素放入 tick
中进行渲染
function tick() {
ReactDOM.render(
<Clock date={new Date()} />,
document.getElementById('root')
)
}
setInterval(tick, 1000);
在这个例子中,我们将计时功能代码封装成了一个独立的可复用的组件,并通过属性date的方式将参数传入,但还不能到达我们想要的结果,那就是不能再组件内部修改参数值,组件中显示的数据依旧受控于父组件中date
属性传递过来的值,那如果我们把这个date
属性也添加到Clock
内部呢?来看下
ReactDOM.render(
<Clock />,
document.getElementById('root')
)
这时父组件中只保留了对计时组件Clock
的一个单纯的引用。剩下的事情全部依托以组件Clock
自己去实现。要怎么实现这个需求?这里React提出了另一个数据对象,即state
,它用来保存组件内部的数据,与props
类似,不同的是state
是组件私有的,并且由组件本身完全控制。它能实现数据在组件内部的修改和更新。怎么使用这个state
?继续往下讲之前我们先拓展一个知识
我们知道组件有两种定义方式,即函数式组件和类组件,虽然函数式组件更加简洁更加接近原生 javascript
,但类组件却拥有一些额外的属性,这个类组件专有特性,就是状态和生命周期钩子,到这里也能清楚知道状态的关键作用,然而函数式组件没有这两个特性,因此,在需要使用到状态state
情况下,我们需要将函数式组件转换成类组件
函数式组件转化成类组件
尝试把一个函数式组件转化成类组件,官网给出了以下步骤,以Clock
组件为例
- 创建一个继承自
React.Component
类的ES6 class
同名类 - 添加一个名为
render()
的空方法 - 把原函数中的所有内容移至
render()
中 - 在
render()
方法中使用this.props
替代props
- 删除保留的空函数声明
class Clock extents React.Component {
render() {
return (
<div>
<h1>Hello, world</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
)
}
}
到此,Clock
组件已经成功被我们修改成了一个类组件,我们便可以在其中添加本地状态state
和生命周期钩子
class Clock extends React.Component {
// 用类构造函数constructor初始化 this.state
constructor(props) {
// 使用super()将props传递给基础构造函数
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
)
}
}
这样,我们的类组件Clock
就拥有了自己的属性 this.state.date
,也就不需要引用组件向其传递值了,因此,我么可以把组件引用中的date
属性删掉,最终,我们将其渲染到DOM上,只使用组件引用,其他都交给组件Clock
自己去实现
ReactDOM.render(
<Clock />,
document.getElementById('root')
)
到这里就结束了?细心的你会发现,组件Clock
只是实现了当前时间的显示,而我们要改装的功能是一个计时器,计时功能去哪里了?没实现啊?我们需要在组件Clock
中找到一个合适的时机去实现这个功能,为此,React团队引入了 声明周期方法,也叫生命周期钩子
在类组件中添加生命周期方法
在一个具有许多组件的应用程序中,在组件被销毁时释放所占用的资源是非常重要的。就像浏览器的垃圾回收机制,近期内不需要再用的资源,应该及时清除。
当 Clock
第一次渲染到DOM时,我们要设置一个定时器 。 这在 React 中称为 “挂载(mounting)” 。它有一个生命钩子componentDidMount()
当 Clock
产生的 DOM 被销毁时,我们也想清除该计时器。 这在 React 中称为 “卸载(unmounting)” 。它的生命钩子是componentWillUnmount()
我们的计时器是在页面加载之后,页面生成初始化状态,然后由计时器去触发状态的刷新,因此,在挂载完成是去设置计时器是个非常不错的选择
componentDidMount() {
this.timerID = setInterval(
() => this.tick(), 1000
)
}
这样我们就实现了组件计时功能,或许你注意到了,在该例中,我们把timerID
存放在this
中而不是this.state
中。
其实,this.props
和this.state
也是数据对象与普通对象一样用来存放数据,只是他们被React团队赋予了新的职能, this.props
由React本身设定,用来存放在组件引用时的属性键值对对象集,不允许Coder们自己去修改;而this.state
也具有特殊的含义,即存放组件本身的、用于视觉输出的数据,但也不是说在编写React程序的时候就必须用用这两个,我们依然可以自己定义普通的数据结构。
既然state
是用于存放组件视觉输出的数据,那在render()
方法中没有被引用的,就不应该出现在state
中了。
养成良好的编码习惯,编写好计时器时,及时的编写卸载事件。卸载时我们清除的数据也是从this
中拿的。
componentWillUnmount() {
clearInterval(this.timerID);
}
挂载时我们声明了一个tick()
方法,接下来我们就要实现这个方法,是用来触发UI更新。嗯哼?UI更新?我们的页面状态state
不是已经更新了吗?为啥还要UI更新?
这里有一个非常重要的方法:setState()
。我们先把代码补充完整再说明
componentDidMount() {
// ...
}
tick() {
this.setState({
date: new Date()
})
}
componentWillUnmount() {
// ...
}
setState()
是React触发页面更新的第二个办法,第一个办法开篇即说过,即render()
方法。setState
作用就是通知React检查带状态的组件中是否含有脏值。此时react会生成一个虚拟DOM与之前的版本进行对比,只有有必要更新时才会更新。关于 state 与 setState过程 在我的另一篇文章中有详细说明,有兴趣的可以翻过去看看。
为什么不把tick()
方法写到componentDidMount()
中?因为tick()
只是一个普通方法,他不需要在生命周期中触发,也不用自动触发。只要谁调用了触发即可。因此不需要也不能放在生命周期钩子函数中。
现在这个时钟每秒都会走了。整理一下,我们整个计时器代码如下:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
整个流程的执行过程是这样的:
-
当
<Clock />
被传入ReactDOM.render()
时, React 会调用Clock
组件的构造函数。 因为Clock
要显示的是当前时间,所以它将使用包含当前时间的对象来初始化this.state
。我们稍后会更新此状态。 -
然后 React 调用了
Clock
组件的render()
方法。 React 从该方法返回内容中得到要显示在屏幕上的内容。然后,React 然后更新 DOM 以匹配Clock
的渲染输出。 -
当
Clock
输出被插入到 DOM 中时,React 调用componentDidMount()
生命周期钩子。在该方法中,Clock
组件请求浏览器设置一个定时器来一次调用tick()
。 -
浏览器会每隔一秒调用一次
tick()
方法。在该方法中,Clock
组件通过setState()
方法并传递一个包含当前时间的对象来安排一个 UI 的更新。通过setState()
, React 得知了组件state
(状态)的变化, 随即再次调用render()
方法,获取了当前应该显示的内容。 这次,render()
方法中的this.state.date
的值已经发生了改变, 从而,其输出的内容也随之改变。React 于是据此对 DOM 进行更新。 -
如果通过其他操作将
Clock
组件从 DOM 中移除了, React 会调用componentWillUnmount()
生命周期钩子, 所以计时器也会被停止。
正确的使用State(状态)
对于setState()
有三件事情是我们应该要知道的
(1)不要直接修改state
真正触发React对比不同版本的虚拟DOM是setState()
方法,直接修改state
页面不会刷新,这一点与原生javascript
区别较大,需要理解。
// 这么做不会触发React更新页面
this.state.comment = 'hello';
// 使用 setState() 代替
this.setState({ comment: 'hello' });
【注意】在组件中,唯一可以初始化分配this.state
的地方就是构造函数constructor(){}
(2)state(状态)更新可能是异步的
React为了优化性能,有可能会将多个setState()
调用合并为一次更新。这就导致 this.props
和this.state
可能是异步更新的,你不能依赖他们的值计算下一个state(状态)
// counter 计数更新会失败
this.setState({
counter: this.state.counter this.props.increment
})
如果我们有这种需求,可以使用以下setState()
办法:
// ES6 箭头函数法
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
// 常规函数法
this.setState(function(prevState, props) {
return {
counter: prevState.counter + props.increment
};
})
(3)state(状态)更新会被合并
当你调用setState()
, React将合并你提供的对象到当前状态中。例如,你的状态可能包含几个独立的变量,然后你用几个独立的setState方法去调用更新,如下
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
合并是浅合并,所以,this.setState({comments})
在合并过程中不会改变this.state.posts
的值,但是会完全替换this.state.comments
的值
数据向下流动
无论是作为父组件还是子组件,它都无法或者一个组件是否有状体,同时也不需要关心另一个组件是定义为函数组件还是类组件。这就是为什么state
经常被称为 本地状态 或 封装状态 的原因, 他不能被拥有并设置它的组件以外的任何组件访问。那如果需要访问怎么处理?
(1)作为其子组件的props(属性)
// 在组件中使用
<h2>It is {this.state.date.toLocaleTimeString()}</h2>
// 传递给子组件作为props
<FormattedDate date={this.state.date} />
虽然FormattedDate
组件通过props
接收了date
的值,但它仍然不能获知该值是来自于Clock
的state
, 还是 Clock
的props
, 或者一个手动创建的变量.
这种数据关系,一般称为"从上到下"或"单向"的数据流。任何state(状态)
始终由某个特定组件所有,并且从该state
导出的任何数据 或 UI 只能影响树"下方"的组件
如果把组件树想像为 props(属性)
的瀑布,所有组件的 state(状态)
就如同一个额外的水源汇入主流,且只能随着主流的方向向下流动。
各组件完全独立
借用上文的Clock
组件,我们创建一个App
组件,并在其中渲染三个Clock
:
function App() {
return (
// 之前说过组件只能返回一个根节点,所以用<div>包起来
<div>
<Clock />
<Clock />
<Clock />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
每个Clock
都设立它自己的计时器并独立更新,如果App
中有一个数据变量,也能被三个Clock
相互独立修改。
至于何时使用有状态组件,何时使用无状态组件,被认为是组件的一个实现细节,取决于你当时的需求,你可以在有状态组件中使用无状态组件,也可以在无状态组件中使用有状态组件
$ 事件处理
通过 React 元素处理事件跟在 DOM 元素上处理事件非常相似。但是有一些语法上的区别:
- React 事件使用驼峰命名,而不是全部小写
- 通过 JSX , 传递一个函数作为事件处理程序,而不是一个字符串
// html usage
<button onclick="todo()">click me</button>
// React usage
<button onClick={todo}>click me></button>
- 在React中不能通过返回
false
来阻止默认行为。必须明确的调用preventDefault
// html usage
<a href="#" onclick="console.log('clicked'); return false">
Click me
</a>
// React usage
class ActionLink extends React.Component {
function handleClick(e) {
e.preventDefault();
console.log('clicked.');
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
)
}
在这里,React团队帮Coder们实现了e事件的跨浏览器兼容问题。当使用React时,我们也不需要调用addEventListener
在DOM 元素被创建后添加事件监听器。相反,只要当元素被初始渲染的时候提供一个监听器就可以了。
当使用ES6类定义一个组件时,通常的一个事件处理程序就是类上的一个方法,看个例子,Toggle
组件渲染一个按钮,让用户在 “ON” 和 “OFF” 状态之间切换:
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// 这个绑定是必要的,使`this`在回调中起作用
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')
);
绑定类方法
在JSX回调中你必须注意 this
的指向。 在 JavaScript 中,类方法默认没有 绑定 的。如果你忘记绑定 this.handleClick
并将其传递给onClick
,那么在直接调用该函数时,this
会是 undefined
。
这不是 React 特有的行为;这是 JavaScript
中的函数如何工作的一部分,可以使用属性初始值设置来正确地 绑定(bind
) 回调,但这是实验性做法,不建议使用,以后有可能会废弃,如果你没有使用属性初始化语法
(1)可以在回调中使用一个 arrow functions
class LoginButton extends React.Component {
handleClick() {
console.log('this is: ', this)
}
render() {
// 这个语法确保 `this` 被绑定在 handleClick 中
return (
<button onClick={(e) => this.handleClick(e)}>
Click me
</button>
);
}
}
(2)使用Function.prototype.bind
方法,相对简洁方便
<button onClick={this.handleClick.bind(this)}>
Click me
</button>
传递参数给事件处理程序
在循环内部,通常需要将一个额外的参数传递给事件处理程序,常用的有一下两种方案;
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this.id)}>Delete Row</button>
上面两个例子中,参数 e
作为 React 事件对象将会被作为第二个参数进行传递。通过箭头函数的方式,事件对象必须显式的进行传递,但是通过 bind
的方式,事件对象以及更多的参数将会被隐式的进行传递。
$ 条件渲染
在 React 中,你可以创建不同的组件封装你所需要的行为。然后,只渲染它们之中的一些,取决于你的应用的状态。
整个组件的条件渲染
React 中的条件渲染就可在JS中的条件语句一样,使用JS操作符如if
或者条件控制符来创建渲染当前的元素,并且让React更新匹配的UI。比如我们有一个需求,需要判断用户是否登录,来显示不同组件
function UserGreeting(props) {
return <h1>Welcome back!</h1>
}
function GustGrreeting(props) {
return <h1>Please sign up.</h1>
}
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />
}
return <GuestGreeting />
}
ReactDOM.render(
<Greeting isLoggedIn={false} />,
document.getElementById('root')
);
使用元素变量条件渲染部分内容
你可以用变量来存储元素。这可以帮助您有条件地渲染组件的一部分,而输出的其余部分不会更改。下方两个组件用于显示登出和登入按钮
function LoginButton() {
return(
<button onClick={props.onClick}>Login</button>
)
}
function LogoutButton(props) {
return (
<button onClick={props.onclick}>Logout</button>
)
}
登入登出按钮已做好,接下来需要实现有切换功能的一个有状态的组件,为了更系统化学习,我们把前面的Greeting
组件一起加进来
class LoginControl extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoginedIn: 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.bind(this)} />
} else {
button = <LoginButton onclick={this.handleLoginClick.bind(this)} />
}
return (
<div>
<Greeting isLoggedIn={isLoggedIn} />{button}</div>
</div>
)
}
}
reactDOM.render(
<LoginControl />,
document.getElementById('root')
)
使用if
是很常见的一种做法,当然也有一些更简短的语。JSX
中有几种内联条件的方法,
(1)使用逻辑与&&操作符的内联if用法
我们可以在 JSX
中嵌入任何表达式,方法是将其包裹在花括号中,同样适用于JS的逻辑与&&运算符
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
<div>
<h1>Hello!</h1>
{ unreadMeaasges.length > 0 &&
<h2> You have {unreadMessages.length} unread messages.
}
</div>
)
}
cosnt message = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
<Mailbox unreadMessages={messages} />,
document.getElementById('root')
);
该案例是可以正常运行的,因为在 JavaScript
中, true && expression
总是会评估为 expression
,而 false && expression
总是执行为 false
。并且我们可以在表达式中嵌入表达式
(2)使用条件操作符的内联If-Else
条件操作符 即三目表达式:condition ? trueExpression : falseExpression
// 条件渲染字符串
<div>The user is {isLoggedIn ? 'currently' : 'not'} logged in.</div>
// 条件渲染组件
<div>
{isLoggedIn ? (
<LogoutButton onClick={this.handleLogoutClick} />
) : (
<LoginButton onClick={this.handleLoginClick} />
)}
</div>
总之,遵循一个原则,哪种方式易于阅读,就选择哪种写法。并且,但条件变得越来越复杂时,可能是提取组件的好时机。
阻止组件渲染
在极少数情况下,您可能希望组件隐藏自身,即使它是由另一个组件渲染的。为此,返回 null
而不是其渲染输出。注意这里是不渲染,不是不显示。
在下面的例子中,根据名为warn
的 props
值,呈现 <WarningBanner />
。如果 props
值为 false
,则该组件不渲染:
function WarningBanner(props) {
if (props.warn) {
return null;
}
return (
<div className="warning">Warning</div>
)
}
class Page extends React.Component {
constructor(props) {
super(props);
this.state = { showWarning: true }
}
handleToggleClick() {
this.setState(prevState => ({
showWarning: !prevState.showWarning
}));
}
render() {
return (
<div>
<Warningbanner warn={this.state.showWarning} />
<button onClick={this.handleToggleClick.bind(this)}>
{ this.state.showWarning ? 'Hide' : 'Show'}
</button>
</div>
)
}
}
ReactDOM.render(
<Page />,
document.getElementById('root')
)
从组件的 render
方法返回 null
不会影响组件生命周期方法的触发。 例如, componentWillUpdate
和componentDidUpdate
仍将被调用。因此需要组件刚载入时就要判断执行返回null
$ 后语
本文为React系统性需学习上半文,下半文主要包括:
- 列表(
List
) 和 键(keys
) - 表单(
Forms
) - 状态提升(
Lifting State Up
) - 组合 VS 继承 (
Composition vs inheritance
)
如果有错误,欢迎大家指正,也欢迎大家到评论区交流共同进步
网友评论