React 目录:
文章目录
前言
React是很好的前端资源库
推荐阅读
整个部分
- 环境准备
- 概论:React-组件,props和state
- 游戏完善:了解常用技术
- 时间旅行:了解React的独特优势
搭建本地环境
1. 安装Node.JS
官网:NodeJS
2. 创建一个新的React项目
React工具链的作用
帮助完成任务:
- 扩展文件和组件的规模
- 使用来自npm的第三方库
- 尽早发现常见错误
- 在开发中实时编辑
CSS
和JS
- 优化生产输出
不使用工具链
不习惯使用JavaScript
工具,可以把React
作为普通的<script>
标记添加到HTML
页面上,以及使用可选的JSX
推荐的工具链
- 创建一个新的单页应用:
Create React App
- 用
Node.js
构建服务端渲染的网站:Next.js
- 构建面向内容的静态网站:
Gatsby
- 打造组件库或将
React
集成到现有代码仓库:后面更灵活的工具链
Create React App
- 用于学习React的舒适环境
- 创建新的单页应用的最佳方式
- 要求:
Node >= 8.10
和npm >= 5.6
创建项目:
npx create-react-app my-app
cd my-app
npm start
npx
是运行工具Create React App
:- 不会处理后端逻辑或者操纵数据库
- 创建一个前端构建流水线(Build Pipeline),可以搭配任何后端
- 在内部使用
Babel
和webpack
- 部署好后,执行:
npm run build
build
文件夹内会生成应用的优化版本- 可以从
ReadMe
了解更多详细设置
Next.js
- 推荐阅读:Next
- 轻量级框架,用于配合
React
打造静态化和服务端渲染应用 - 以
Node.js
为服务器环境,包含样式和路由方案
Gatsby
更灵活的工具链
适合更有经验的使用者
Neutrino
把Webpack
的强大功能和简单预设结合在一起,并包含了应用和组件的预设Nx
是针对全栈monorepo
的开发工具包,内置了React
,Next.js
,Express
等Parcel
是快速,零配置的网页应用打包器Razzle
是一个无需配置的服务端渲染框架,提供了比Next.js
更多的灵活性
从头开始打造工具链
组成:
- 一个
Package
管理器:比如Yarn
和npm
,管理三方库 - 一个打包器:
Webpack
和Parcel
,组合成模块,优化加载时间 - 一个编译器:
Babel
,编写最新版本的代码,旧版浏览器依然能使用
3. 删除新项目中的src
所有文件
- 不要删除
src
文件夹,而只删除里面的源文件 - 在
src/
文件夹中创建index.css
,index.js
body {
font: 14px "Century Gothic", Futura, sans-serif;
margin: 20px;
}
ol, ul {
padding-left: 30px;
}
.board-row:after {
clear: both;
content: "";
display: table;
}
.status {
margin-bottom: 10px;
}
.square {
background: #fff;
border: 1px solid #999;
float: left;
font-size: 24px;
font-weight: bold;
line-height: 34px;
height: 34px;
margin-right: -1px;
margin-top: -1px;
padding: 0;
text-align: center;
width: 34px;
}
.square:focus {
outline: none;
}
.kbd-navigation .square:focus {
background: #ddd;
}
.game {
display: flex;
flex-direction: row;
}
.game-info {
margin-left: 20px;
}
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
class Square extends React.Component {
render() {
return (
<button className="square">
{
/* TODO */}
</button>
);
}
}
class Board extends React.Component {
renderSquare(i) {
return <Square />;
}
render() {
const status = 'Next player: X';
return (
<div>
<div className="status">{
status}</div>
<div className="board-row">
{
this.renderSquare(0)}
{
this.renderSquare(1)}
{
this.renderSquare(2)}
</div>
<div className="board-row">
{
this.renderSquare(3)}
{
this.renderSquare(4)}
{
this.renderSquare(5)}
</div>
<div className="board-row">
{
this.renderSquare(6)}
{
this.renderSquare(7)}
{
this.renderSquare(8)}
</div>
</div>
);
}
}
class Game extends React.Component {
render() {
return (
<div className="game">
<div className="game-board">
<Board />
</div>
<div className="game-info">
<div>{
/* status */}</div>
<ol>{
/* TODO */}</ol>
</div>
</div>
);
}
}
// ========================================
ReactDOM.render(
<Game />,
document.getElementById('root')
);
4. 运行
- 运行:
npm start
- 访问:
localhost:3000
CDN
链接
开发环境
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
生产环境
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
核心概念
1. 入门
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
);
2. JSX
变量声明
const element = <h1>Hello, world!</h1>;
- 它被称为 JSX,是一个 JavaScript 的语法扩展
在JSX
嵌入表达式
const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;
ReactDOM.render(
element,
document.getElementById('root')
);
- 在 JSX 语法中,你可以在大括号内放置任何有效的 JavaScript 表达式。例如,
2 + 2
,user.firstName
或formatName(user)
都是有效的 JavaScript 表达式
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>;
const element = <img src={user.avatarUrl}></img>;
JSX
指定子元素
const element = <img src={user.avatarUrl} />;
const element = (
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
);
JSX
防止注入攻击
const title = response.potentiallyMaliciousInput;
// 直接使用是安全的:
const element = <h1>{title}</h1>;
- React DOM 在渲染所有输入内容之前,默认会进行转义。它可以确保在你的应用中,永远不会注入那些并非自己明确编写的内容,所有的内容在渲染之前都被转换成了字符串
- 这样可以有效地防止 XSS(cross-site-scripting, 跨站脚本)攻击
JSX
表示对象
- Babel 会把 JSX 转译成一个名为
React.createElement()
函数调用 - 两种示例代码完全等效
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
ReactDOM.render(
element,
document.getElementById('root')
);
React.createElement()
会预先执行一些检查,以帮助你编写无错代码,但实际上它创建了一个这样的对象:
// 注意:这是简化过的结构
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world!'
}
};
- 这些对象被称为 “React 元素”
- 它们描述了你希望在屏幕上看到的内容。React 通过读取这些对象,然后使用它们来构建 DOM 以及保持随时更新
3. 元素渲染
const element = <h1>Hello, world</h1>;
- 与浏览器的 DOM 元素不同,React 元素是创建开销极小的普通对象
- React DOM 会负责更新 DOM 来与 React 元素保持一致
- 组件是由元素构成的
将一个元素渲染为 DOM
<div id="root"></div>
- 我们将其称为“根” DOM 节点,因为该节点内的所有内容都将由 React DOM 管理
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);
4. 组件 & Props
- 参考详细组件 API
- 组件,从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素
函数组件和class
组件
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
- 该函数是一个有效的 React 组件,因为它接收唯一带有数据的 “props”(代表属性)对象与并返回一个 React 元素
- 这类组件被称为“函数组件”,因为它本质上就是 JavaScript 函数
ES6
定义组件
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
渲染组件
const element = <div />;
const element = <Welcome name="Sara" />;
- 当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”
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>
。
注意:
- React 会将以小写字母开头的组件视为原生 DOM 标签
<div />
代表 HTML 的 div 标签,而<Welcome />
则代表一个组件,并且需在作用域内使用 Welcome
组合组件
- 组件可以在其输出中引用其他组件
- 这就可以让我们用同一组件来抽象出任意层次的细节。按钮,表单,对话框,甚至整个屏幕的内容:在 React 应用程序中,这些通常都会以组件的形式表示
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
这样的小组件,并自下而上地将这类组件逐步应用到视图层的每一处
提取组件
- 将组件拆分为更小的组件
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>
);
}
提取Avatar
组件:
function Avatar(props) {
return (
<img className="Avatar"
src={props.user.avatarUrl}
alt={props.user.name}
/>
);
}
修改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
:
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>
);
}
Props
的只读性
- 组件无论是使用函数声明还是通过 class 声明,都决不能修改自身的 props
- 所有 React 组件都必须像纯函数一样保护它们的 props 不被更改
function sum(a, b) {
return a + b;
}
5. State
& 生命周期
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);
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
- State 与 props 类似,但是 state 是私有的,并且完全受控于当前组件
将函数组件转换成 class 组件
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
- 每次组件更新时
render
方法都会被调用,但只要在相同的 DOM 节点中渲染<Clock />
,就仅有一个Clock
组件的 class 实例被创建使用 - 这就使得我们可以使用如 state 或生命周期方法等很多其他特性
向 class 组件中添加局部的 state
- 把
render()
方法中的this.props.date
替换成this.state.date
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
- 添加一个 class 构造函数,然后在该函数中为
this.state
赋初值
class Clock extends React.Component {
constructor(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 />
元素中的date
属性
class Clock extends React.Component {
constructor(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')
);ReactDOM.render(
<Clock />,
document.getElementById('root')
);
将生命周期方法添加到 Class 中
- 在具有许多组件的应用程序中,当组件被销毁时释放所占用的资源是非常重要的
- 当
Clock
组件第一次被渲染到 DOM 中的时候,就为其设置一个计时器。这在 React 中被称为“挂载(mount)” - 同时,当 DOM 中
Clock
组件被删除的时候,应该清除计时器。这在 React 中被称为“卸载(unmount)”
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
}
componentWillUnmount() {
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
生命周期方法
componentDidMount()
方法会在组件已经被渲染到 DOM 中后运行,所以,最好在这里设置计时器
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
tick() {
this.setState({
date: new Date()
});
}
在 componentWillUnmount()
生命周期方法中清除计时器
componentWillUnmount() {
clearInterval(this.timerID);
}
最终结果:
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')
);
setState
- 不要直接修改
state
// Wrong
this.state.comment = 'Hello';
- 使用
setState
// Correct
this.setState({comment: 'Hello'});
- 构造函数是唯一可以给
this.state
赋值的地方
- State 的更新可能是异步的
- 因为
this.props
和this.state
可能会异步更新,所以你不要依赖他们的值来更新下一个状态
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
// Correct
this.setState(function(state, props) {
return {
counter: state.counter + props.increment
};
});
State 的更新会被合并
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
6. 事件处理
事件
html
:
<button onclick="activateLasers()">
Activate Lasers
</button>
React
:
<button onClick={activateLasers}>
Activate Lasers
</button>
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>
);
}
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')
);
向事件处理程序传递参数
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
7. 条件渲染
原组件:
function UserGreeting(props) {
return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
return <h1>Please sign up.</h1>;
}
React
:
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>
);
}
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 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
渲染多个组件
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li>{number}</li>
);
ReactDOM.render(
<ul>{listItems}</ul>,
document.getElementById('root')
);
基础列表组件
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
属性
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>
);
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
const todoItems = todos.map((todo, index) =>
// Only do this if items have no stable IDs
<li key={index}>
{todo.text}
</li>
);
用 key
提取组件
function ListItem(props) {
const value = props.value;
return (
// 错误!你不需要在这里指定 key:
<li key={value.toString()}>
{value}
</li>
);
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// 错误!元素的 key 应该在这里指定:
<ListItem value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
正确
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')
);
9. 表单
<form>
<label>
名字:
<input type="text" name="name" />
</label>
<input type="submit" value="提交" />
</form>
受控组件
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('提交的名字: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
名字:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="提交" />
</form>
);
}
}
textarea
<textarea>
你好, 这是在 text area 里的文本
</textarea>
class EssayForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '请撰写一篇关于你喜欢的 DOM 元素的文章.'
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('提交的文章: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
文章:
<textarea value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="提交" />
</form>
);
}
}
select
<select>
<option value="grapefruit">葡萄柚</option>
<option value="lime">酸橙</option>
<option selected value="coconut">椰子</option>
<option value="mango">芒果</option>
</select>
class FlavorForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: 'coconut'};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('你喜欢的风味是: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
选择你喜欢的风味:
<select value={this.state.value} onChange={this.handleChange}>
<option value="grapefruit">葡萄柚</option>
<option value="lime">酸橙</option>
<option value="coconut">椰子</option>
<option value="mango">芒果</option>
</select>
</label>
<input type="submit" value="提交" />
</form>
);
}
}
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>
<legend>Enter temperature in Celsius:</legend>
<input
value={temperature}
onChange={this.handleChange} />
<BoilingVerdict
celsius={parseFloat(temperature)} />
</fieldset>
);
}
}
11. 组合 和 继承
- 推荐使用组合而非继承来实现组件间的代码重用
包含关系
- 有些组件无法提前知晓它们子组件的具体内容。在
Sidebar
(侧边栏)和Dialog
(对话框)等展现通用容器(box)的组件中特别容易遇到这种情况 - 我们建议这些组件使用一个特殊的
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>
);
}
对应的灵活prop
:
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">
{props.left}
</div>
<div className="SplitPane-right">
{props.right}
</div>
</div>
);
}
function App() {
return (
<SplitPane
left={
<Contacts />
}
right={
<Chat />
} />
);
}
特例关系
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!" />
);
}
组合
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
{props.children}
</FancyBorder>
);
}
class SignUpDialog extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSignUp = this.handleSignUp.bind(this);
this.state = {login: ''};
}
render() {
return (
<Dialog title="Mars Exploration Program"
message="How should we refer to you?">
<input value={this.state.login}
onChange={this.handleChange} />
<button onClick={this.handleSignUp}>
Sign Me Up!
</button>
</Dialog>
);
}
handleChange(e) {
this.setState({login: e.target.value});
}
handleSignUp() {
alert(`Welcome aboard, ${this.state.login}!`);
}
}
性能优化
针对生产环境进行正确配置
使用生产版本
- 推荐开发应用时使用开发模式,为用户部署时使用生产模式
确保使用压缩后的生产版本: - 对
React
应用进行BenchMark
- 性能问题
检查是否使用生产版本: - Chrome的React开发者工具
Create React App
通过Create react app
构建的项目:
- 生产运行:
npm run build
- 将在
build/
目录中生成对应的生产版本 - 只有在生产部署前才需要执行这个命令
- 将在
- 开发运行:
npm start
单文件构建
生产环境使用的单文件版:
<script src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
- 只有
.production.min.js
适用于生产
Brunch
安装terser-brunch
插件,获取最高效的生产构建:
# 如果使用npm
npm install --save-dev terser-brunch
# 如果使用Yarn
yarn add --dev terser-brunch
创建生产构建:
brunch build -p
- 开发中使用,则会变得卡慢
Browserify
高效生产构建的插件:
# 如果使用npm
npm install --save-dev envify terser uglifyify
# Yarn
yarn add --dev envify terser uglifyify
envify
转换器用于设置正确的环境变量,全局设置-g
uglifyify
转换器移除开发相关的引用代码,全局-g
sample:
browserify ./index.js ¥
-g [ envify --NODE_ENV production ] ¥
-g uglifyify ¥
| terser --compress --mangle > ./bundle.js
webpack
- 推荐阅读:webpack官方文档
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
mode: 'production',
optimization: {
minimizer: [new TerserPlugin({
/* addtional options here */})]
}
};