一、JSX语法
1、花括号 { } 把任意的 [JavaScript 表达式]嵌入到 JSX 中
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')
);
2、JSX可作表达式
可以在 if 语句或者是 for 循环中使用 JSX,用它给变量赋值,当做参数接收,或者作为函数的返回值
function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user)}!</h1>;
}
return <h1>Hello, Stranger.</h1>;
}
3、用 JSX 指定属性值
(1)用双引号 “” 来指定字符串字面量作为属性值
(2)用花括号 {} 嵌入一个 JavaScript 表达式作为属性值
在属性中嵌入 JavaScript 表达式时,不要使用引号来包裹大括号。否则,JSX 将该属性视为字符串字面量而不是表达式。对于字符串值你应该使用引号,对于表达式你应该使用大括号,但两者不能同时用于同一属性。
const element = <div tabIndex="0"></div>;
const element = <img src={user.avatarUrl}></img>;
4、用 JSX 指定子元素
(1)如果是空标签,您应该像 XML 一样,使用 />立即闭合它
(2)JSX 标签可能包含子元素
const element = <img src={user.avatarUrl} />;
const element = (
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
);
5、JSX 表示对象
Babel 将JSX编译成 React.createElement() 调用。
//两种形式等价
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
二、元素渲染
元素(Elements)是 React 应用中最小的构建部件(或者说构建块,building blocks),不同于组件。
const element = <h1>Hello, world</h1>;
1、渲染一个元素到 DOM
要渲染一个 React 元素到一个 root DOM 节点,把它们传递给 ReactDOM.render() 方法:
const element = <h1>Hello, world</h1>;
ReactDOM.render(
element,
document.getElementById('root')
);
2 、更新已渲染的元素
React 元素是不可突变的. 一旦你创建了一个元素, 就不能再修改其子元素或任何属性。一个元素就像电影里的一帧: 它表示在某一特定时间点的 UI 。更新 UI 的唯一方法是创建一个新的元素, 并将其传入 ReactDOM.render()方法.
3、React 只更新必需要更新的部分
React DOM 会将元素及其子元素与之前版本逐一对比, 并只对有必要更新的 DOM 进行更新, 以达到 DOM 所需的状态。
三、组件(Components) 和 属性(Props)
组件使你可以将 UI 划分为一个一个独立,可复用的小部件,并可以对每个部件进行单独的设计。
1、函数式组件和类组件
//函数式组件
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
//类组件
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
2、渲染组件
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
- 我们调用了 ReactDOM.render() 方法并向其中传入了 <Welcome name="Sara" /> 元素。
- React 调用 Welcome 组件,并向其中传入了 {name: 'Sara'} 作为 props 对象。
- Welcome 组件返回 <h1>Hello, Sara</h1>。
- React DOM 迅速更新 DOM ,使其显示为 <h1>Hello, Sara</h1>。
注意:
-
组件名称总是以大写字母开始。
<div /> 代表一个 DOM 标签,而 <Welcome /> 则代表一个组件,并且需要在作用域中有一个 Welcome 组件。 - 组件必须返回一个单独的根元素。这就是为什么我们添加一个 <div> 来包含所有 <Welcome /> 元素的原因。
function App() {
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
3、提取组件
提取组件可能看起来是一个繁琐的工作,但是在大型的 Apps 中可以回报给我们的是大量的可复用组件。一个好的经验准则是如果你 UI 的一部分需要用多次 (Button,Panel,Avatar),或者本身足够复杂(App,FeedStory,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>
);
}
4、所有 React 组件都必须是纯函数,并禁止修改其自身 props
四、状态(State) 和 生命周期
1、把函数式组件转化为类组件
遵从以下5步, 把一个类似 Clock这样的函数式组件转化为类组件:
- 创建一个继承自 React.Component类的 ES6 class 同名类。
- 添加一个名为 render() 的空方法。
- 把原函数中的所有内容移至 render() 中。
- 在 render() 方法中使用 this.props 替代 props。
- 删除保留的空函数声明。
Clock 现在被定为类组件,而不是函数式组件。
类允许我们在其中添加本地状态(state)和生命周期钩子。
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
2、在类组件中添加本地状态(state)
我们现在通过以下3步, 把date从属性(props) 改为 状态(state):
class Clock extends React.Component {
2、添加一个 类构造函数(class constructor)初始化 this
constructor(props) {
super(props);
this.state = {date: new Date()};
}
1、替换 render() 方法中的 this.props.date 为 this.state.date
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
3、移除 <Clock /> 元素中的 date 属性
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
3、在类中添加生命周期方法
在一个具有许多组件的应用程序中,在组件被销毁时释放所占用的资源是非常重要的。
当 Clock 第一次渲染到DOM时,我们要设置一个定时器。 这在 React 中称为 “挂载(mounting)” 。
当 Clock 产生的 DOM 被销毁时,我们也想 清除该计时器 。 这在 React 中称为 “卸载(unmounting)” 。
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
1、this.props 由 React 本身设定, 而 this.state 具有特殊的含义,但如果需要存储一些不用于视觉输出的内容,则可以手动向类中添加额外的字段。
如果在 render() 方法中没有被引用, 它不应该出现在 state 中。
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
2、我们在componentWillUnmount()生命周期钩子中取消这个计时器
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() 生命周期钩子, 所以计时器也会被停止。
4、正确地使用 State(状态)
setState() 有三件事是你应该知道的:
(1)用 setState() 设置状态:
this.setState({comment: 'Hello'});
(2)state(状态) 更新可能是异步的:
React 为了优化性能,有可能会将多个 setState() 调用合并为一次更新。
因为 this.props 和 this.state 可能是异步更新的,你不能依赖他们的值计算下一个state(状态)。
例如, 以下代码可能导致 counter(计数器)更新失败:
// 错误
this.setState({
counter: this.state.counter + this.props.increment,
});
要解决这个问题,应该使用第 2 种 setState() 的格式,它接收一个函数,而不是一个对象。该函数接收前一个状态值作为第 1 个参数, 并将更新后的值作为第 2个参数:
要弥补这个问题,使用另一种 setState() 的形式,它接受一个函数而不是一个对象。这个函数将接收前一个状态作为第一个参数,应用更新时的 props 作为第二个参数:
// 正确
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
// 正确
this.setState(function(prevState, props) {
return {
counter: prevState.counter + props.increment
};
});
(3)state(状态)更新会被合并
当你调用 setState(), React 将合并你提供的对象到当前的状态中。
例如,你的状态可能包含几个独立的变量:
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
然后通过调用独立的 setState() 调用分别更新它们:
合并是浅合并,所以 this.setState({comments}) 不会改变 this.state.posts 的值,但会完全替换this.state.comments 的值。
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
5、数据向下流动
无论作为父组件还是子组件,它都无法获悉一个组件是否有状态,同时也不需要关心另一个组件是定义为函数组件还是类组件。
这就是 state(状态) 经常被称为 本地状态 或 封装状态的原因。 它不能被拥有并设置它的组件 以外的任何组件访问。
一个组件可以选择将 state(状态) 向下传递,作为其子组件的 props(属性):
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
同样适用于用户定义组件:
<FormattedDate date={this.state.date} />
FormattedDate 组件通过 props(属性) 接收了 date 的值,但它仍然不能获知该值是来自于 Clock的 state(状态) ,还是 Clock 的 props(属性),或者是直接手动创建的:
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
这通常称为一个“从上到下”,或者“单向”的数据流。任何 state(状态) 始终由某个特定组件所有,并且从该 state(状态) 导出的任何数据 或 UI 只能影响树中 “下方” 的组件。
如果把组件树想像为 props(属性) 的瀑布,所有组件的 state(状态) 就如同一个额外的水源汇入主流,且只能随着主流的方向向下流动。
五、处理事件
1、与普通HTML区别
(1) React 事件使用驼峰命名,而不是全部小写。
(2)通过 JSX , 你传递一个函数作为事件处理程序,而不是一个字符串。
<button onClick={activateLasers}>
Activate Lasers
</button>
(3)在 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>
);
}
(4)当使用一个 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')
);
2、将参数传递给事件处理程序
在循环内部,通常需要将一个额外的参数传递给事件处理程序。 例如,如果 id 是一个内联 ID,则以下任一方式都可以正常工作:
等价的,分别使用 arrow functions 和 Function.prototype.bind
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
上面两个例子中,参数 e 作为 React 事件对象将会被作为第二个参数进行传递。通过箭头函数的方式,事件对象必须显式的进行传递,但是通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。
六、条件渲染
1、在函数式组件中用if判断
我们需要创建一个 Greeting 组件, 用来根据用户是否登录, 判断并显示上述两个组件之一:
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
ReactDOM.render(
// 修改为 isLoggedIn={true} 试试:
<Greeting isLoggedIn={false} />,
document.getElementById('root')
);
2、元素变量
在接下来的例子中,我们将会创建一个有状态组件,叫做 LoginControl 。
它将渲染 <LoginButton /> 或者 <LogoutButton /> ,取决于当前状态。同时渲染前面提到的 <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')
);
3、使用逻辑 && 操作符的内联 if 用法
(1)可以在JSX中嵌入任何表达式,方法是将其包裹在花括号中
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')
);
(2)条件操作符 condition ? 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>
);
}
七、列表(Lists) 和 键(Keys)
1、基本列表组件
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')
);
2、键(Keys)
键(Keys) 帮助 React 标识哪个项被修改、添加或者移除了。数组中的每一个元素都应该有一个唯一不变的键(Keys)来标识:
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
(1)使用 keys 提取组件
keys 只在数组的上下文中存在意义。
例如,如果你提取一个 ListItem 组件,应该把 key 放置在数组处理的 <ListItem /> 元素中,不能放在 ListItem 组件自身中的 <li> 根元素上。
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')
);
(2)keys 在同辈元素中必须是唯一的
在数组中使用的 keys 必须在它们的同辈之间唯一。然而它们并不需要全局唯一。我们可以在操作两个不同数组的时候使用相同的 keys
(3)键是React的一个内部映射,但其不会传递给组件的内部。
如果你需要在组件中使用相同的值,可以明确使用一个不同名字的 prop 传入。