概述
核心概念
2. JSX
JS 扩展
const element = <h1>Hello, world!</h1>;
视图与逻辑之间的结合
Babel 会把 JSX 转译成一个名为 React.createElement()
函数调用
以下两种示例代码完全等效:
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
// 预先执行一些检查,以帮助你编写无错代码
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
// 实际上它创建了一个这样的对象
// 注意:这是简化过的结构
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world!'
}
};
这些对象被称为 “React 元素”
它们描述了你希望在屏幕上看到的内容
React 通过读取这些对象,然后使用它们来构建 DOM 以及保持随时更新
3. 元素渲染进根节点
元素:构成 React 应用的最小砖块 ,描述了你在屏幕上想看到的内容
将一个元素渲染为 DOM
假设你的 HTML 文件某处有一个 <div>
:
<div id="root"></div>
想要将一个 React 元素渲染到根 DOM 节点中,只需把它们一起传入 ReactDOM.render()
const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('root'));
4. 组件 & Props
组件,类似函数, 接受 “props” = > React 元素 (展页面示内容)
定义组件
1️⃣ 编写 JavaScript 函数
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
2️⃣ 使用 ES6 的 class 来定义组件
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
组件名称必须以
大写字母
开头
不能更改props
不能修改自身的 props。来看下这个 sum
函数:
function sum(a, b) {
// 纯函数:不会尝试更改入参
return a + b;
}
function withdraw(account, amount) {
// 不是纯函数:更改了自己的入参
account.total -= amount;
}
所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。
5. State & 生命周期
state
: 私有的,完全受控于当前组件。 是组件自动更新
将函数组件转换成 class 组件
缺点:需要将 ReactDOM.render
放进一个函数,然后使用 setInterval
调用,并且 Clock
需要传入参数
改进:直接调用 ReactDOM.render
并且 Clock
不要参数
方法:将函数组件变为 class 组件,state 类似与 props
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);
- 创建一个同名的 ES6 class,并且继承于 React.Component。
- 添加一个空的
render()
方法。 - 将函数体移动到
render()
方法之中。 - 在
render()
方法中使用this.props
替换props
// 1
class Clock extends React.Component {
// 2
render() {
// 3
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
// 4 this.props
}
}
向 class 组件中添加局部的 state
我们通过以下三步将 date
从 props 移动到 state 中:
- 把
render()
方法中的this.props.date
替换成this.state.date
: - 添加一个 class 构造函数,然后在该函数中为 this.state 赋初值
- 移除
<Clock />
元素中的date
属性
class Clock extends React.Component {
constructor(props) {
// 将 props 传递到父类的构造函数中
// Class 组件应该始终使用 props 参数来调用父类的构造函数
super(props);
this.state = {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')
);
将生命周期方法添加到 Class 中
在具有许多组件的应用程序中,当组件被销毁时释放所占用的资源是非常重要的。
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
// 当 Clock 组件第一次被渲染到 DOM 中的时候,就为其设置一个计时器。这在 React 中被称为“挂载(mount)”
componentDidMount() {
// 把计时器的 ID 保存在 this.timerID 之中
// 尽管 this.props 和 this.state 是 React 本身设置的,且都拥有特殊的含义,但是其实你可以向 class 中随意添加不参与数据流(比如计时器 ID)的额外字段
this.timerID = setInterval(
() => this.tick(),
1000
);
}
// 当 DOM 中 Clock 组件被删除的时候,应该清除计时器。这在 React 中被称为“卸载(unmount)”
componentWillUnmount() {
// 清除计时器
clearInterval(this.timerID);
}
// Clock 组件每秒都会调用它
tick() {
// 使用 this.setState() 来时刻更新组件 state
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')
);
正确地使用 State
关于 setState()
你应该了解三件事:
不要直接修改 State
例如,此代码不会重新渲染组件:
// Wrong
this.state.comment = 'Hello';
而是应该使用 setState()
:
// Correct
this.setState({comment: 'Hello'});
构造函数是唯一可以给 this.state
赋值的地方:
State 的更新可能是异步的
出于性能考虑,React 可能会把多个 setState()
调用合并成一个调用。
因为 this.props
和 this.state
可能会异步更新,所以你不要依赖他们的值来更新下一个状态。
例如,此代码可能会无法更新计数器:
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
要解决这个问题,可以让 setState()
接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数:
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
上面使用了箭头函数,不过使用普通的函数也同样可以:
// Correct
this.setState(function(state, props) {
return {
counter: state.counter + props.increment
};
});
State 的更新会被合并
当你调用 setState()
的时候,React 会把你提供的对象合并到当前的 state。
例如,你的 state 包含几个独立的变量:
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
然后你可以分别调用 setState()
来单独地更新它们:
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
。
6. 事件处理
在 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 元素添加监听器
事实上,你只需要在该元素初始渲染的时候添加监听器即可
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// 为了在回调中使用 `this`,这个绑定是必不可少的
// 在 JavaScript 中,class 的方法默认不会绑定 this。
this.handleClick = this.handleClick.bind(this);
}
// 当你使用 ES6 class 语法定义一个组件的时候,通常的做法是将事件处理函数声明为 class 中的方法
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')
);
不使用 bind
的另一种方法,可以在回调中使用箭头函数
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
// 此语法确保 `handleClick` 内的 `this` 已被绑定。
return (
<button onClick={() => this.handleClick()}>
Click me
</button>
);
}
}
向事件处理程序传递参数
在循环中,通常我们会为事件处理函数传递额外的参数。例如,若 id
是你要删除那一行的 ID,以下两种方式都可以向事件处理函数传递参数:
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
7. 条件渲染
登陆退出改变文字
import React from 'react'
import ReactDOM from 'react-dom'
// 两种行为
function UserGreeting(props) {
return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
return <h1>Please sign up.</h1>;
}
// 呈现两种行为的方式
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
// 两个 button 样式
function LoginButton(props) {
return (
<button onClick={props.onClick}>
Login
</button>
);
}
function LogoutButton(props) {
return (
<button onClick={props.onClick}>
Logout
</button>
);
}
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;
// isLoggedIn 决定 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')
);
点击隐藏 Warning
import React from 'react'
import ReactDOM from 'react-dom'
// 传入的状态决定是否展示内容到界面上
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')
);
8. 列表 & Key
import React from 'react'
import ReactDOM from 'react-dom'
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) => <li key={number}>{number}</li>);
ReactDOM.render(
<ul>{listItems}</ul>, document.getElementById('root')
);
import React from 'react'
import ReactDOM from 'react-dom'
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 帮助 React 识别哪些元素改变了
,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。
元素的 key 只有放在就近的数组上下文中
才有意义。
比方说,如果你提取出一个 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')
);
10. 状态提升
计算水在给定温度下是否会沸腾
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
}
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = { temperature: '' };
}
handleChange(e) {
this.setState({ temperature: e.target.value });
}
render() {
const temperature = this.state.temperature;
return (
// fieldset:对表单进行分组,一个表单可以有多个fieldset
// legend:说明每组的内容描述
<fieldset>
<legend>Enter temperature in Celsius:</legend>
{/* value: 显示的值为 temperature */}
<input
value={temperature}
onChange={this.handleChange} />
<BoilingVerdict
celsius={parseFloat(temperature)} />
</fieldset>
);
}
}
两个输入框内的数值彼此能够同步
状态提升:将多个组件中需要共享的 state 向上移动到它们的最近共同父组件中,便可实现共享 state
import React from 'react'
import ReactDOM from 'react-dom'
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
}
const scaleNames = {
c: 'Celsius',
f: 'Fahrenheit'
};
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = { temperature: '' };
}
handleChange(e) {
// Before: this.setState({temperature: e.target.value});
this.props.onTemperatureChange(e.target.value);
}
render() {
// Before: const temperature = this.state.temperature;
// 自定义组件中的 temperature 和 onTemperatureChange 这两个 prop 的命名没有任何特殊含义。
// 我们可以给它们取其它任意的名字,例如,把它们命名为 value 和 onChange 就是一种习惯。
const temperature = this.props.temperature;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature}
// handlechange 在每次按键时都会执行并更新 React 的 props
onChange={this.handleChange} />
</fieldset>
);
}
}
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheit(celsius) {
return (celsius * 9 / 5) + 32;
}
function tryConvert(temperature, convert) {
const input = parseFloat(temperature);
if (Number.isNaN(input)) {
return '';
}
const output = convert(input);
// Math.round()函数: 返回一个数字四舍五入后最接近的整数
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
}
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
this.state = {temperature: '', scale: 'c'};
}
handleCelsiusChange(temperature) {
// 调用 this.setState() 进而请求 React 重新渲染自己本身。
this.setState({scale: 'c', temperature});
}
handleFahrenheitChange(temperature) {
this.setState({scale: 'f', temperature});
}
render() {
const scale = this.state.scale;
const temperature = this.state.temperature;
const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;
return (
<div>
<TemperatureInput
scale="c"
// 父组件 Calculator 提供参数
temperature={celsius}
// 无论哪个输入框被编辑都会调用 Calculator 组件中对应的方法。 然后会调用 this.setState()
onTemperatureChange={this.handleCelsiusChange} />
<TemperatureInput
scale="f"
temperature={fahrenheit}
onTemperatureChange={this.handleFahrenheitChange} />
<BoilingVerdict
celsius={parseFloat(celsius)} />
</div>
);
}
}
ReactDOM.render(
<div>
<Calculator/>
</div>,
document.getElementById('root')
);
11. 组合 vs 继承
包含关系
使用一个特殊的 children
prop 来将他们的子组件传递到渲染结果中
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
Welcome
</h1>
<p className="Dialog-message">
Thank you for visiting our spacecraft!
</p>
</FancyBorder>
);
}
在一个组件中预留出几个“洞”。这种情况下,我们可以不使用 children
,而是自行约定:将所需内容传入 props,并使用相应的 prop
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">
{props.left}
</div>
<div className="SplitPane-right">
{props.right}
</div>
</div>
);
}
// <Contacts /> 和 <Chat /> 之类的 React 元素本质就是对象(object),所以你可以把它们当作 props,像其他数据一样传递
function Contacts(){
return(
<div>Contacts</div>
)
}
function Chat(){
return(
<div>Chat</div>
)
}
function App() {
return (
<SplitPane
left={
<Contacts />
}
right={
<Chat />
} />
);
}
特例关系
有些时候,我们会把一些组件看作是其他组件的特殊实例,比如 WelcomeDialog
可以说是 Dialog
的特殊实例。
在 React 中,我们也可以通过组合来实现这一点。“特殊”组件可以通过 props 定制并渲染“一般”组件:
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
</FancyBorder>
);
}
function WelcomeDialog() {
return (
<Dialog
title="Welcome"
message="Thank you for visiting our spacecraft!" />
);
}
最后
以上就是炙热毛巾为你收集整理的React 官网核心概念核心概念的全部内容,希望文章能够帮你解决React 官网核心概念核心概念所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复