react
声明式,高效且灵活地用于构建用户界面的JS库。使用react可以将一些简短、独立的代码片段组合成复杂的UI界面,这些代码片段被称作组件
helloworld
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
);
JSX
const element = <h1>Hello, world!</h1>;
这个有趣的标签语法既不是字符串也不是 HTML。
它被称为 JSX,是一个 JavaScript 的语法扩展。
JSX 可以生成 React “元素”。
在JSX中嵌入表达式
在下面的例子中,我们声明了一个名为 name
的变量,然后在 JSX 中使用它,并将它包裹在大括号中:
const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;
ReactDOM.render(
element,
document.getElementById('root')
);
在下面的示例中,我们将调用 JavaScript 函数 formatName(user)
的结果,并将结果嵌入到 <h1>
元素中。
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特定属性
你可以通过使用引号,来将属性值指定为字符串字面量:
const element = <div tabIndex="0"></div>;
也可以使用大括号,来在属性值中插入一个 JavaScript 表达式:
const element = <img src={user.avatarUrl}></img>;
使用JSX指定子元素
假如一个标签里面没有内容,你可以使用 />
来闭合标签,就像 XML 语法一样:
const element = <img src={user.avatarUrl} />;
JSX 标签里能够包含很多子元素:
const element = (
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
);
JSX表示对象
Babel 会把 JSX 转译成一个名为 React.createElement()
函数调用。
以下两种示例代码完全等效:
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
React.createElement()
会预先执行一些检查,以帮助你编写无错代码,但实际上它创建了一个这样的对象:
// 注意:这是简化过的结构
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world!'
}
};
这些对象被称为 “React 元素”。它们描述了你希望在屏幕上看到的内容。React 通过读取这些对象,然后使用它们来构建 DOM 以及保持随时更新。
元素渲染
元素是构成react应用的最小砖块
元素描述了你再屏幕上想看到的内容
const element = <h1>Hello, world</h1>;
与浏览器的 DOM 元素不同,React 元素是创建开销极小的普通对象。React DOM 会负责更新 DOM 来与 React 元素保持一致。
将一个元素渲染成dom
假设你的 HTML 文件某处有一个 <div>
:
<div id="root"></div>
我们将其称为“根” DOM 节点,因为该节点内的所有内容都将由 React DOM 管理。
仅使用 React 构建的应用通常只有单一的根 DOM 节点。如果你在将 React 集成进一个已有应用,那么你可以在应用中包含任意多的独立根 DOM 节点。
想要将一个 React 元素渲染到根 DOM 节点中,只需把它们一起传入 ReactDOM.render()
:
const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('root'));
更新已渲染的元素
React 元素是不可变对象。一旦被创建,你就无法更改它的子元素或者属性。一个元素就像电影的单帧:它代表了某个特定时刻的 UI。
根据我们已有的知识,更新 UI 唯一的方式是创建一个全新的元素,并将其传入 ReactDOM.render()
。
考虑一个计时器的例子:
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(element, document.getElementById('root'));}
setInterval(tick, 1000);
react只更新它需要更新的部分
组件&Props
组件允许将UI拆分成独立可复用的代码片段,并对每个片段进行独立构思
函数组件与class组件
定义组件最简单的方式就是编写 JavaScript 函数:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
该函数是一个有效的 React 组件,因为它接收唯一带有数据的 “props”(代表属性)对象与并返回一个 React 元素。这类组件被称为“函数组件”,因为它本质上就是 JavaScript 函数。
同时还可以使用 ES6 的 class 来定义组件:
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
上述两个组件在 React 里是等效的。
渲染组件
之前,我们遇到的 React 元素都只是 DOM 标签:
const element = <div />;
不过,React 元素也可以是用户自定义的组件:
const element = <Welcome name="Sara" />;
当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”。
例如,这段代码会在页面上渲染 “Hello, Sara”:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
组合组件
组件可以在其输出中引用其他组件。这就可以让我们用同一组件来抽象出任意层次的细节。按钮,表单,对话框,甚至整个屏幕的内容:在 React 应用程序中,这些通常都会以组件的形式表示。
例如,我们可以创建一个可以多次渲染 Welcome
组件的 App
组件:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
function App() {
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
通常来说,每个新的 React 应用程序的顶层组件都是 App
组件。但是,如果你将 React 集成到现有的应用程序中,你可能需要使用像 Button
这样的小组件,并自下而上地将这类组件逐步应用到视图层的每一处。
提取组件
将组件拆分为更小的组件。
例如,参考如下 Comment
组件:
function Comment(props) {
return (
<div className="Comment">
<div className="UserInfo">
<img className="Avatar"
src={props.author.avatarUrl}
alt={props.author.name}
/>
<div className="UserInfo-name">
{props.author.name}
</div>
</div>
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
该组件用于描述一个社交媒体网站上的评论功能,它接收 author
(对象),text
(字符串)以及 date
(日期)作为 props。
该组件由于嵌套的关系,变得难以维护,且很难复用它的各个部分。因此,让我们从中提取一些组件出来。
首先,我们将提取 Avatar
组件:
function Avatar(props) {
return (
<img className="Avatar"
src={props.user.avatarUrl}
alt={props.user.name}
/> );
}
Avatar
不需知道它在 Comment
组件内部是如何渲染的。因此,我们给它的 props 起了一个更通用的名字:user
,而不是 author
。
我们建议从组件自身的角度命名 props,而不是依赖于调用组件的上下文命名。
我们现在针对 Comment
做些微小调整:
function Comment(props) {
return (
<div className="Comment">
<div className="UserInfo">
<Avatar user={props.author} />
<div className="UserInfo-name">
{props.author.name}
</div>
</div>
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
接下来,我们将提取 UserInfo
组件,该组件在用户名旁渲染 Avatar
组件:
function UserInfo(props) {
return (
<div className="UserInfo">
<Avatar user={props.user} />
<div className="UserInfo-name">
{props.user.name}
</div>
</div> );
}
进一步简化 Comment
组件:
function Comment(props) {
return (
<div className="Comment">
<UserInfo user={props.author} />
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
State & 生命周期
通过调用 ReactDOM.render()
来修改我们想要渲染的元素:
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById('root')
);
}
setInterval(tick, 1000);
通过clock组件可以设置计时器进行组件更新
我们可以从封装时钟的外观开始:
function Clock(props) {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {props.date.toLocaleTimeString()}.</h2>
</div> );
}
function tick() {
ReactDOM.render(
<Clock date={new Date()} />,
document.getElementById('root')
);
}
setInterval(tick, 1000);
然而,它忽略了一个关键的技术细节:Clock
组件需要设置一个计时器,并且需要每秒更新 UI。
理想情况下,我们希望只编写一次代码,便可以让 Clock
组件自我更新:
ReactDOM.render(
<Clock />, document.getElementById('root')
);
我们需要在 Clock
组件中添加 “state” 来实现这个功能。
State 与 props 类似,但是 state 是私有的,并且完全受控于当前组件。
将函数组件转换成class组件
通过以下五步将 Clock
的函数组件转成 class 组件:
- 创建一个同名的 ES6 class,并且继承于
React.Component
。 - 添加一个空的
render()
方法。 - 将函数体移动到
render()
方法之中。 - 在
render()
方法中使用this.props
替换props
。 - 删除剩余的空函数声明。
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
现在 Clock
组件被定义为 class,而不是函数。
每次组件更新时 render
方法都会被调用,但只要在相同的 DOM 节点中渲染 <Clock />
,就仅有一个 Clock
组件的 class 实例被创建使用。这就使得我们可以使用如 state 或生命周期方法等很多其他特性。
TODO
事件处理
react元素的事件处理和DOM元素的很相似,但有一点语法上的不同
- React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
- 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
例如,传统的 HTML:
<button onclick="activateLasers()">
Activate Lasers
</button>
在 React 中略微不同:
<button onClick={activateLasers}>
Activate Lasers
</button>
在 React 中另一个不同点是你不能通过返回 false
的方式阻止默认行为。你必须显式的使用 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 时,你一般不需要使用 addEventListener
为已创建的 DOM 元素添加监听器。事实上,你只需要在该元素初始渲染的时候添加监听器即可。
当你使用 ES6 class 语法定义一个组件的时候,通常的做法是将事件处理函数声明为 class 中的方法。例如,下面的 Toggle
组件会渲染一个让用户切换开关状态的按钮:
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// 为了在回调中使用 `this`,这个绑定是必不可少的
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(state => ({
isToggleOn: !state.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
你必须谨慎对待 JSX 回调函数中的 this
,在 JavaScript 中,class 的方法默认不会绑定 this
。如果你忘记绑定 this.handleClick
并把它传入了 onClick
,当你调用这个函数的时候 this
的值为 undefined
。
这并不是 React 特有的行为;这其实与 JavaScript 函数工作原理有关。通常情况下,如果你没有在方法后面添加 ()
,例如 onClick={this.handleClick}
,你应该为这个方法绑定 this
。
如果觉得使用 bind
很麻烦,这里有两种方式可以解决。如果你正在使用实验性的 public class fields 语法,你可以使用 class fields 正确的绑定回调函数:
class LoggingButton extends React.Component {
// 此语法确保 `handleClick` 内的 `this` 已被绑定。
// 注意: 这是 *实验性* 语法。
handleClick = () => {
console.log('this is:', this);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
如果你没有使用 class fields 语法,你可以在回调中使用箭头函数:
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
// 此语法确保 `handleClick` 内的 `this` 已被绑定。 return ( <button onClick={() => this.handleClick()}> Click me
</button>
);
}
}
此语法问题在于每次渲染 LoggingButton
时都会创建不同的回调函数。在大多数情况下,这没什么问题,但如果该回调函数作为 prop 传入子组件时,这些组件可能会进行额外的重新渲染。我们通常建议在构造器中绑定或使用 class fields 语法来避免这类性能问题。
向事件处理程序传递参数
在循环中,通常我们会为事件处理函数传递额外的参数。例如,若 id
是你要删除那一行的 ID,以下两种方式都可以向事件处理函数传递参数:
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
上述两种方式是等价的,分别通过箭头函数和 Function.prototype.bind
来实现。
条件渲染
在react中,可以创建不同的组件来封装各种行为,然后依据应用的不同状态,只渲染对应状态下的部分内容
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')
);
元素变量
可以使用变量来存储元素。它可以有条件地渲染组件的一部分,而其他的渲染部分不会因此改变
// 登录组件
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;
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')
);
与运算符&&
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
);
}
const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
<Mailbox unreadMessages={messages} />,
document.getElementById('root')
);
三目运算符
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>
);
}
阻止组件渲染
如果warn的值是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};
this.handleToggleClick = this.handleToggleClick.bind(this);
}
handleToggleClick() {
this.setState(state => ({
showWarning: !state.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')
);
列表& key
通过map()函数让数组中的每一项变双倍,然后就得到了一个新的列表doubled
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((number) => number * 2);
console.log(doubled);
渲染多个组件
可以通过使用{}
在JSX内构件一个元素集合
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li>{number}</li>
);
我们把整个listItems
插入到<ul>
元素中,然后渲染进DOM
ReactDOM.render(
<ul>{listItems}</ul>,
document.getElementById('root')
);
基础列表组件
我们可以把前面的例子重构成一个组件,这个组件接收numbers数组作为参数并输出一个元素列表
function 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')
);
当我们运行这段代码,将会看到一个警告
a key should be provided for list items
,意思是当你创建一个元素时,必须包括一个特殊的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 numbers={numbers} />,
document.getElementById('root')
);
key
key帮助react识别哪些元素修改了,比如被添加或删除。因此应该给数组中每一个元素赋予一个确定的标识
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
一个元素的key最好是这个元素在列表中拥有的一个独一无二的字符串。通常,我们使用数据中的id来作为元素的key
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
当元素没有确定id时,万不得已可以使用元素索引index作为key
const todoItems = todos.map((todo, index) =>
// Only do this if items have no stable IDs
<li key={index}>
{todo.text}
</li>
);
使用key提取组件
元素的key只有放在就近的数组上下文中才有意义
比如提取出一个ListItem
组件,应该吧key保留在数组中的这个<ListItem />
元素上,而不是放在ListItem
组件的<li>
元素上
- 不正确的key使用方法 ```jsx function ListItem(props) { const value = props.value; return ( // 错误!你不需要在这里指定 key:
{value}
);
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// 错误!元素的 key 应该在这里指定:
-
{listItems}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
- 正确的key使用方法
```jsx
function ListItem(props) {
// 正确!这里不需要指定 key:
return <li>{props.value}</li>;
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// 正确!key 应该在数组的上下文中被指定
<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()
方法中的元素需要设置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
在jsx中嵌入map()
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<ListItem key={number.toString()}
value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
JSX允许在大括号中嵌入任何表达式,可以内联map()返回结果
function NumberList(props) {
const numbers = props.numbers;
return (
<ul>
{numbers.map((number) =>
<ListItem key={number.toString()}
value={number} />
)}
</ul>
);
}