React-学习指南

React 目录:


前言

React是很好的前端资源库

推荐阅读

整个部分

  1. 环境准备
  2. 概论:React-组件,props和state
  3. 游戏完善:了解常用技术
  4. 时间旅行:了解React的独特优势

搭建本地环境

1. 安装Node.JS

官网:NodeJS

2. 创建一个新的React项目

React工具链的作用

帮助完成任务:

  • 扩展文件和组件的规模
  • 使用来自npm的第三方库
  • 尽早发现常见错误
  • 在开发中实时编辑CSSJS
  • 优化生产输出

不使用工具链

不习惯使用JavaScript工具,可以把React作为普通的<script>标记添加到HTML页面上,以及使用可选的JSX

推荐的工具链

  • 创建一个新的单页应用:Create React App
  • Node.js构建服务端渲染的网站:Next.js
  • 构建面向内容的静态网站:Gatsby
  • 打造组件库或将React集成到现有代码仓库:后面更灵活的工具链

Create React App

  • 用于学习React的舒适环境
  • 创建新的单页应用的最佳方式
  • 要求:Node >= 8.10npm >= 5.6
    创建项目:
npx create-react-app my-app
cd my-app
npm start
  • npx是运行工具
  • Create React App
    • 不会处理后端逻辑或者操纵数据库
    • 创建一个前端构建流水线(Build Pipeline),可以搭配任何后端
    • 在内部使用Babelwebpack
  • 部署好后,执行:npm run build
  • build文件夹内会生成应用的优化版本
  • 可以从ReadMe了解更多详细设置

Next.js

  • 推荐阅读:Next
  • 轻量级框架,用于配合React打造静态化和服务端渲染应用
  • Node.js为服务器环境,包含样式和路由方案

Gatsby

  • 推荐阅读:
  • 创建静态网站的最佳方式
  • 保证可以使用React组件
  • 输出预渲染的HTMLCSS以保证最快的加载速度

更灵活的工具链

适合更有经验的使用者

  • NeutrinoWebpack的强大功能和简单预设结合在一起,并包含了应用和组件的预设
  • Nx是针对全栈monorepo的开发工具包,内置了React, Next.js, Express
  • Parcel是快速,零配置的网页应用打包器
  • Razzle是一个无需配置的服务端渲染框架,提供了比Next.js更多的灵活性

从头开始打造工具链

组成:

  • 一个Package管理器:比如Yarnnpm,管理三方库
  • 一个打包器:WebpackParcel,组合成模块,优化加载时间
  • 一个编译器: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 + 2user.firstNameformatName(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')
);

过程:

  1. 我们调用 ReactDOM.render() 函数,并传入 <Welcome name="Sara" /> 作为参数。
  2. React 调用 Welcome 组件,并将 {name: 'Sara'} 作为 props 传入。
  3. Welcome 组件将 <h1>Hello, Sara</h1> 元素作为返回值。
  4. 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的只读性

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

  1. 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>
    );
  }
}
  1. 添加一个 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>
    );
  }
}
  1. 移除 <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

  1. 不要直接修改state
// Wrong
this.state.comment = 'Hello';
  1. 使用setState
// Correct
this.setState({comment: 'Hello'});
  • 构造函数是唯一可以给 this.state 赋值的地方
  1. State 的更新可能是异步的
  • 因为 this.propsthis.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
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
    
    
    mode: 'production',
    optimization: {
    
    
        minimizer: [new TerserPlugin({
    
     /* addtional options here */})]
    }
};

猜你喜欢

转载自blog.csdn.net/u013362192/article/details/114677117