1.什么是React #

React是Facebook开发并在2013年开源的一款JavaScript库,用于构建用户界面,尤其是单页应用。它允许开发者通过组合多个组件来创建复杂的UI。

2.为什么使用React #

  1. 组件化:React以组件为单位构建界面,这使得代码复用和维护变得简单且方便。每个组件都有自己的状态和逻辑,可以独立渲染并且可以轻松地嵌套在其他组件中。

  2. 声明式设计:React采用声明式编程模型,开发者只需要描述应用程序在任何特定状态下的UI应该呈现的样子,而无需担心如何从一个状态转换到另一个状态。React会自动管理UI的更新。

  3. 虚拟DOM:React引入了虚拟DOM的概念,当应用的状态改变时,React会创建一个新的虚拟DOM,与上一个虚拟DOM进行比较,然后只更新有差异的部分。这可以极大地提高应用的性能。

  4. React生态:React拥有丰富的生态系统,有大量的开源库和工具可以使用,如Redux、React Router等。此外,社区活跃,你可以在遇到问题时得到快速的帮助。

3. React的用途 #

React主要用于构建用户界面。由于它的灵活性,它可以用于开发各种应用,例如:

4.搭建React开发环境 #

4.1 创建目录 #

mkdir 1.react
cd 1.react
npm init -y

4.2 安装依赖 #

npm install --save react react-dom 
npm install --save-dev webpack webpack-cli webpack-dev-server 
npm install --save-dev @babel/core @babel/preset-env @babel/preset-react babel-loader
包名 作用
react 这是React库本身,用于构建用户界面的JavaScript库。
react-dom 这个包提供了用于在Web页面上处理React的DOM操作的方法。
webpack webpack是一个模块捆绑器,它把你的代码打包成一个或多个文件,并且可以进行代码分割、懒加载等操作。
webpack-cli webpack命令行接口,允许你在命令行中运行webpack。
webpack-dev-server 为你提供了一个简单的web服务器,并且能够实时重新加载(live reload)。
@babel/core Babel的核心编译器,主要用于将ES6/ES7的语法转化为浏览器能识别的ES5语法。
@babel/preset-env Babel的插件,根据你的目标环境,将ES6/ES7/ES8代码编译成ES5。
@babel/preset-react Babel的插件,用于转译JSX语法。
babel-loader Babel的一个Webpack插件,让你可以在Webpack中使用Babel。
html-webpack-plugin html-webpack-plugin 是一个用于 webpack 构建的插件,它能够根据配置生成一个 HTML 文件,并将打包后的 JavaScript 文件自动添加到生成的 HTML 文件中。

4.3 webpack配置文件 #

webpack.config.js

// 引入 Node.js 的 path 模块,这个模块提供了一些用于处理文件和目录的路径的工具。
const path = require('path');
// 引入 html-webpack-plugin,这个插件可以根据指定模板生成HTML文件,并自动注入打包后的js文件。
const HtmlWebpackPlugin = require('html-webpack-plugin');
// 导出一个对象,这个对象是 webpack 的配置对象。
module.exports = {
    // 配置应用程序的入口点,webpack 会从这个文件开始构建。
    entry: './src/index.js',
    // 配置 webpack 如何输出结果的相关选项。
    output: {
        // 使用 Node.js 的 path 模块的 join 方法,指定编译后的文件的位置。
        // __dirname 是当前文件的目录,dist 是新创建的文件夹。
        path: path.join(__dirname, 'dist'),
        // 输出文件的名称。
        filename: 'main.js'
    },
    // 配置 loader,loader 可以将所有类型的文件转换为 webpack 能够处理的模块。
    module: {
        rules: [
            {
                // 使用正则表达式匹配 JavaScript 文件和 JSX 文件。
                test: /\.(js|jsx)$/,
                // 排除 node_modules 目录,我们不需要编译 node_modules 下的文件。
                exclude: /node_modules/,
                // 对于所有匹配的文件,使用 babel-loader 进行转换。
                // options 指定了 babel 的配置选项,其中 presets 指定了需要使用的 Babel 预设。
                use: [{
                    loader: 'babel-loader',
                    options: { "presets": ["@babel/preset-env", "@babel/preset-react"] }
                }],
            },
        ],
    },
    // 配置插件,插件可以用于执行范围更广的任务。
    plugins: [
        // 使用 HtmlWebpackPlugin,该插件会自动创建一个新的 index.html 文件,
        // 并把打包后的 JavaScript 文件自动添加到这个新创建的 index.html 文件中。
        new HtmlWebpackPlugin({
            template: './src/index.html'
        })
    ]
};

4.4 src\index.js #

src\index.js

// 引入 react-dom 库的 createRoot 方法。
//这是 React 18 中引入的新特性,它为并发模式(Concurrent Mode)提供了新的渲染方法。
import { createRoot } from 'react-dom';
// 使用 createRoot 方法来初始化一个根 ReactDOM。
//getElementById('root') 获取到一个 id 为 'root' 的 DOM 节点,
//createRoot 创建了一个对应的 React 根渲染节点。
//然后调用 render 方法,在这个根节点上渲染一个字符串 "hello"。
createRoot(document.getElementById('root')).render("hello");

4.5 src\index.html #

src\index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>React App</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

4.6 package.json #

{
  "scripts": {
    "start": "webpack serve --mode development",
    "build": "webpack --mode production"
  }
}

5.JSX渲染 #

5.1 什么是元素 #

在React中,元素是构建应用程序的最小单位。元素是描述在屏幕上看到的内容的对象。每一个React元素都表示了一个DOM节点或者是一个组件。

React元素是不可变的。一旦你为元素创建了UI,就无法更改其子元素或者属性。一个元素就像电影的一帧:它代表了某个特定时刻的UI。

这就是为什么更新UI时,React需要创建一个新的元素。然后,React将这个新的元素与旧的元素进行比较,确定如何最有效地更新UI。

React元素的创建非常简单,可以通过React的createElement方法来创建。例如:

const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

以上代码创建了一个表示标题元素的React元素。元素类型为'h1',元素属性是一个具有className键和'greeting'值的对象,元素的子元素是字符串'Hello, world!'。

5.2 什么是JSX #

在实际的React代码中,你可能会使用JSX来创建元素,这看起来更接近HTML。例如:

const element = <h1 className="greeting">Hello, world!</h1>;

这行代码做了与前面的createElement方法相同的事情,但它更易读、易写。在编译时,JSX将被转化为普通的JavaScript函数调用和对象。

JSX (JavaScript XML) 是一个 JavaScript 的语法扩展,它允许你使用类似于 HTML 的语法来创建 React 元素。这使得构建 UI 更为直观和更易读写。JSX 经常被误认为是一种模板语言,但实际上它完全是 JavaScript。

以下是一个 JSX 例子:

const element = <h1>Hello, World!</h1>;

这段代码定义了一个简单的 React 元素,其类型是 h1,包含文本 "Hello, World!"。

JSX 在 JavaScript 中并不是内建的,因此它需要被转译(transpile)成为普通的 JavaScript 才能被浏览器理解。这个转译工作通常是由 babel等工具完成的。

注意,JSX 中的标签名(例如 <h1>)将被转化为 React 函数调用。大写字母开头的 JSX 标签,如 <MyComponent />,对应的是 React 组件。

JSX 还有一些其他特性需要了解:

总的来说,JSX 提供了一种更为直观和易读写的方式来定义 React 元素。虽然它并非必须使用,但是它极大地提升了 React 的开发体验。

5.3 index.js #

src\index.js

// 导入React库
import React from 'react'; 
 // 导入ReactDOM库的client部分
import {createRoot} from 'react-dom/client';
// 创建一个React元素,一个带有样式和类名的div
// 这个div包含一个span子元素和一个字符串'world'
let element = (
  <div className="title" style={{ color: "red" }}>
    <span>hello</span>world
  </div>
);
// 创建一个新的root,root是连接React和DOM的桥梁
const root = createRoot(document.getElementById('root'));
// 渲染element到root对应的DOM节点 
root.render(element);

6.组件 #

6.1 函数组件 #

React的函数组件是一种简单的组件类型,它是一个接收属性(props)并返回React元素的JavaScript函数。它们是无状态的,意味着它们不具备内部状态(除非使用React的Hooks)。

下面是一个函数组件的例子:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

在这个例子中,Welcome就是一个函数组件。你可以像使用普通React元素那样使用它:

import React from 'react'; 
import {createRoot} from 'react-dom/client';
function Welcome(props) {
    return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="zhufeng" />;
const root = createRoot(document.getElementById('root'));
root.render(element);

这将会在页面上显示“Hello, zhufeng”。

注意,函数组件的名称必须以大写字母开始,这是由React的JSX解析规则决定的。这样,React就能区分本地组件(如<Welcome />)和HTML元素(如<div />)。

6.2 类(定义的)组件 #

类组件是另一种定义组件的方式

类组件是使用ES6的类语法来定义的,它们必须扩展React.Component,并且必须定义一个名为render的方法。render方法应该返回需要渲染的内容。

src\index.js

// 导入React库
import React from 'react';
import { createRoot } from 'react-dom/client';
+class Welcome extends React.Component {
+    render() {
+        return <h1>Hello, {this.props.name}</h1>;
+    }
+}
+const element = <Welcome name="zhufeng" />;
const root = createRoot(document.getElementById('root'));
root.render(element);

7.类组件的更新 #

7.1 组件状态 #

在 React 中,类组件可以有自己的状态,这个状态通常被称为 "local state" 或者 "component state"。状态是一个 JavaScript 对象,它保存了可能会随时间改变的信息。当状态改变时,组件会重新渲染。

要在类组件中添加状态,你需要在类的构造函数(constructor)中初始化一个名为 this.state 的属性:

class Counter extends React.Component {
  constructor(props) {
    super(props); // 调用父类的构造函数,传递props
    this.state = { count: 0 }; // 初始化状态
  }

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
      </div>
    );
  }
}

在这个例子中,Counter 组件有一个 count 状态。这个状态在构造函数中初始化,然后在 render 方法中被使用。

为了改变状态,你不能直接修改 this.state,而是应该使用 this.setState 方法。这个方法会通知 React 状态已经改变,并且可能需要重新渲染。

例如,你可能想要在点击一个按钮时增加计数:

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  increment = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.increment}>Increment</button>
      </div>
    );
  }
}

在这个例子中,我们定义了一个 increment 方法,这个方法通过 this.setState 增加了 count 状态。然后,我们在一个按钮的 onClick 事件处理器中使用了这个方法。

总的来说,状态是让 React 组件能够动态改变输出的一种方式。在类组件中,你可以通过 this.statethis.setState 来使用状态。

6.合成事件和批量更新 #

6.1 合成事件(Synthetic Events) #

React 实现了自己的事件系统,叫做合成事件系统,这是为了在不同的浏览器环境下提供一致性,并且可以在事件处理程序中重新使用事件对象(这通常在原生事件系统中是不能做到的)。

合成事件提供了和原生事件相同的接口,包括阻止默认行为和阻止事件传播等。当事件触发时,React 会创建一个合成事件对象,并传递给你定义的事件处理程序。在事件处理程序结束后,React 将清理这个对象以重用。

以下是一个使用合成事件的例子:

class MyComponent extends React.Component {
  handleClick = (event) => {
    alert(event.type); // 'click'
  };

  render() {
    return <button onClick={this.handleClick}>Click me</button>;
  }
}

在这个例子中,handleClick 是一个事件处理程序,它接收一个合成事件对象作为参数。在这个事件处理程序中,你可以像在原生事件中那样访问 event.type

6.2 批量更新(Batched Updates) #

React 有一个性能优化的特性,那就是批量更新。当有多个状态更新需要进行时,React 会把它们批量(batch)进行,然后一次性进行重新渲染。这样可以避免不必要的渲染并提高性能。

如果没有批量更新,每次调用setState都会导致组件重新渲染。但是由于 React 的批量更新,多次setState更新可能会被合并成一次

src\index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { number: 0 };
  }
  handleClick = () => {
+    this.setState({ number: this.state.number + 1 });
+    console.log(this.state);
+    this.setState({ number: this.state.number + 1 });
+    console.log(this.state);
+    setTimeout(() => {
+      this.setState({ number: this.state.number + 1 });
+      console.log(this.state);
+      this.setState({ number: this.state.number + 1 });
+      console.log(this.state);
+    });
  }
  render() {
    return (
      <div>
        <p>{this.props.title}</p>
        <p>number:{this.state.number}</p>
        <button onClick={this.handleClick}>+</button>
      </div>
    )
  }
}
let element = <Counter title="计数器" />
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

7.ref #

在React中,ref(引用)是一种属性,可以用来访问并操作DOM元素或者类组件的实例。这通常在你需要从React的数据流之外去操作元素时才会用到。

7.1 为 DOM 元素添加 ref #

Refs 通常使用 React.createRef() 在类组件的构造函数中创建,并赋值给实例属性,以便在整个组件中使用。

要访问 Ref,你需要将其添加到 JSX 中的元素上。这可以通过将 ref 属性设置为你在构造函数中创建的 ref 来完成。

然后,你可以在组件的其他地方通过 this.myRef.current 来访问元素。

src\index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
+class Sum extends React.Component {
+  a
+  b
+  result
+  constructor(props) {
+      super(props);
+      this.a = React.createRef();
+      this.b = React.createRef();
+      this.result = React.createRef();
+  }
+  handleAdd = () => {
+      let a = this.a.current.value;
+      let b = this.b.current.value;
+      this.result.current.value = a + b;
+  }
+  render() {
+      return (
+          <>
+              <input ref={this.a} />+<input ref={this.b} /><button onClick={this.handleAdd}>=</button><input ref={this.result} />
+          </>
+      );
+  }
+}
+let element = <Sum/>
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

7.2 为 class 组件添加 Ref #

在React中,你可以使用ref给类组件创建引用。这样你就可以在其他地方使用这个引用来访问类组件的方法和属性

当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性

src\index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
class Form extends React.Component {
  input
  constructor(props) {
      super(props);
      this.input = React.createRef();
  }
  getFocus = () => {
      this.input.current.getFocus();
  }
  render() {
      return (
          <>
              <TextInput ref={this.input} />
              <button onClick={this.getFocus}>获得焦点</button>
          </>
      );
  }
}
class TextInput extends React.Component {
  input
  constructor(props) {
      super(props);
      this.input = React.createRef();
  }
  getFocus = () => {
      this.input.current.focus();
  }
  render() {
      return <input ref={this.input} />
  }
}
let element = <Form/>
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

7.3 Ref转发 #

src/index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
const TextInput = React.forwardRef((props, ref) => (
  <input ref={ref} />
));
class Form extends React.Component {
  input
  constructor(props) {
      super(props);
      this.input = React.createRef();
  }
  getFocus = () => {
      console.log(this.input.current);

      this.input.current.focus();
  }
  render() {
      return (
          <>
              <TextInput ref={this.input} />
              <button onClick={this.getFocus}>获得焦点</button>
          </>
      );
  }
}
let element = <Form/>
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

8.生命周期 #

react15_1626405471667.jpg

React 组件的生命周期可以分为三个主要阶段:挂载阶段(Mounting)、更新阶段(Updating)和卸载阶段(Unmounting)。

下面是 React 类组件的生命周期方法:

  1. 挂载阶段(Mounting):

当一个组件被插入到 DOM 中时,以下的方法将按照顺序被调用:

  1. 更新阶段(Updating):

当组件的 props 或 state 发生改变时,将触发更新,以下的方法将按照顺序被调用:

  1. 卸载阶段(Unmounting):

当一个组件从 DOM 中被移除时,以下的方法将被调用:

以下是这些方法的简单解释:

请注意,以上的生命周期方法适用于 React 16.3 及以上版本,其中废弃了 componentWillMountcomponentWillReceivePropscomponentWillUpdate 这些方法,而引入了新的生命周期方法,如 getDerivedStateFromPropsgetSnapshotBeforeUpdate

8.1 基本生命周期 #

src\index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
class Counter extends React.Component{
  static defaultProps = {
      name: 'zhufeng'
  };
  constructor(props) {
      super(props);
      this.state = { number: 0 }
      console.log('Counter 1.constructor')
  }
  componentWillMount() {
      console.log('Counter 2.componentWillMount');
  }
  componentDidMount() {
      console.log('Counter 4.componentDidMount');
  }
  handleClick = () => {
      this.setState({ number: this.state.number + 1 });
  };
  shouldComponentUpdate(nextProps, nextState) { 
      console.log('Counter 5.shouldComponentUpdate');
      return nextState.number % 2 === 0;
  } 
  componentWillUpdate() {
      console.log('Counter 6.componentWillUpdate');
  }
  componentDidUpdate() {
      console.log('Counter 7.componentDidUpdate');
  }
  render() {
      console.log('Counter 3.render');
      return (
          <div>
              <p>{this.state.number}</p>
              <button onClick={this.handleClick}>+</button>
          </div>
      )
  }
}
let element = <Counter/>
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

8.2 子组件生命周期 #

src\index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
class Counter extends React.Component {
  static defaultProps = {
    name: 'zhufeng'
  };
  constructor(props) {
    super(props);
    this.state = { number: 0 }
    console.log('Counter 1.constructor')
  }
  UNSAFE_componentWillMount() {
    console.log('Counter 2.componentWillMount');
  }
  componentDidMount() {
    console.log('Counter 4.componentDidMount');
  }
  handleClick = () => {
    this.setState({ number: this.state.number + 1 });
  };
  shouldComponentUpdate(nextProps, nextState) {
    console.log('Counter 5.shouldComponentUpdate');
    return nextState.number % 2 === 0;
  }
  UNSAFE_componentWillUpdate() {
    console.log('Counter 6.componentWillUpdate');
  }
  componentDidUpdate() {
    console.log('Counter 7.componentDidUpdate');
  }
  render() {
    console.log('Counter 3.render');
    return (
      <div>
        <p>{this.state.number}</p>
        {this.state.number === 4 ? null : <ChildCounter count={this.state.number} />}
        <button onClick={this.handleClick}>+</button>
      </div>
    )
  }
}
class ChildCounter extends React.Component {
  componentWillUnmount() {
    console.log(' ChildCounter 6.componentWillUnmount')
  }
  UNSAFE_componentWillMount() {
    console.log('ChildCounter 1.componentWillMount')
  }
  render() {
    console.log('ChildCounter 2.render')
    return (<div>
      {this.props.count}
    </div>)
  }
  componentDidMount() {
    console.log('ChildCounter 3.componentDidMount')
  }
  UNSAFE_componentWillReceiveProps(newProps) {
    console.log('ChildCounter 4.componentWillReceiveProps')
  }
  shouldComponentUpdate(nextProps, nextState) {
    console.log('ChildCounter 5.shouldComponentUpdate')
    return nextProps.count % 3 === 0;
  }
}
let element = <Counter />
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

8.3 新的生命周期 #

react16_1626532331619

8.3.1 getDerivedStateFromProps #

getDerivedStateFromProps 是 React 组件的一个静态生命周期方法,它的主要用途是让组件在 props 改变时更新 state。在 React 16.3 版本引入了这个方法,作为 componentWillReceiveProps 方法的替代。

这个方法在每次渲染过程中都会被调用,包括初始化挂载和后续的更新阶段。它的返回值将被加入到组件的 state 中。如果你返回 null 或者不返回任何内容,那么不会对 state 进行任何更改。

import React from 'react';
import ReactDOM from 'react-dom/client';
class Counter extends React.Component{
  static defaultProps = {
      name: 'zhufeng'
  };
  constructor(props) {
      super(props);
      this.state = { number: 0 }
  }

  handleClick = () => {
      this.setState({ number: this.state.number + 1 });
  };

  render() {
      console.log('3.render');
      return (
          <div>
              <p>{this.state.number}</p>
              <ChildCounter count={this.state.number} />
              <button onClick={this.handleClick}>+</button>
          </div>
      )
  }
}
class ChildCounter extends React.Component {
  constructor(props) {
      super(props);
      this.state = { number: 0 };
  }
  static getDerivedStateFromProps(nextProps, prevState) {
      const { count } = nextProps;
      if (count % 2 === 0) {
          return { number: count * 2 };
      } else {
          return { number: count * 3 };
      }
  }
  render() {
      console.log('child-render', this.state)
      return (
        <div>
            {this.state.number}
        </div>
      )
  }

}
let element = <Counter />
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

8.3.2 getSnapshotBeforeUpdate #

getSnapshotBeforeUpdate 是一个在 React 组件中更新发生之前被调用的生命周期方法。这个方法在最新的渲染输出被提交给 DOM 前会立即调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。

getSnapshotBeforeUpdate 返回的值将作为 componentDidUpdate 的第三个参数。如果你在 getSnapshotBeforeUpdate 中返回了一个值,那么这个值会被传递给 componentDidUpdate。如果你不需要在 componentDidUpdate 中使用这个值,你可以返回 null

import React from 'react';
import ReactDOM from 'react-dom/client';
class ScrollingList extends React.Component {
  constructor(props) {
      super(props);
      this.state = { messages: [] }
      this.wrapper = React.createRef();
  }
  addMessage() {
      this.setState(state => ({
          messages: [`${state.messages.length}`, ...state.messages],
      }))
  }
  componentDidMount() {
      this.timeID = window.setInterval(() => {
          this.addMessage();
      }, 1000)
  }
  componentWillUnmount() {
      window.clearInterval(this.timeID);
  }
  getSnapshotBeforeUpdate() {
      return {prevScrollTop:this.wrapper.current.scrollTop,prevScrollHeight:this.wrapper.current.scrollHeight};
  }
  componentDidUpdate(pervProps, pervState, {prevScrollHeight,prevScrollTop}) {
      this.wrapper.current.scrollTop = prevScrollTop + (this.wrapper.current.scrollHeight - prevScrollHeight);
  }
  render() {
      let style = {
          height: '100px',
          width: '200px',
          border: '1px solid red',
          overflow: 'auto'
      }
      return (
          <div style={style} ref={this.wrapper} >
              {this.state.messages.map((message, index) => (
                  <div key={index}>{message}</div>
              ))}
          </div>
      );
  }
}
let element = <ScrollingList />
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

9.Context(上下文) #

contextapi_1626532435193

src\index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
let ThemeContext = React.createContext();
console.log(ThemeContext);
const { Provider, Consumer } = ThemeContext;
let style = { margin: '5px', padding: '5px' };
function Title(props) {
  console.log('Title');
  return (
    <Consumer>
      {
        (contextValue) => (
          <div style={{ ...style, border: `5px solid ${contextValue.color}` }}>
            Title
          </div>
        )
      }
    </Consumer>
  )
}
class Header extends React.Component {
  static contextType = ThemeContext
  render() {
    console.log('Header');
    return (
      <div style={{ ...style, border: `5px solid ${this.context.color}` }}>
        Header
        <Title />
      </div>
    )
  }
}
function Content() {
  console.log('Content');
  return (
    <Consumer>
      {
        (contextValue) => (
          <div style={{ ...style, border: `5px solid ${contextValue.color}` }}>
            Content
            <button style={{ color: 'red' }} onClick={() => contextValue.changeColor('red')}>变红</button>
            <button style={{ color: 'green' }} onClick={() => contextValue.changeColor('green')}>变绿</button>
          </div>
        )
      }
    </Consumer>
  )
}
class Main extends React.Component {
  static contextType = ThemeContext
  render() {
    console.log('Main');
    return (
      <div style={{ ...style, border: `5px solid ${this.context.color}` }}>
        Main
        <Content />
      </div>
    )
  }
}

class Page extends React.Component {
  constructor(props) {
    super(props);
    this.state = { color: 'black' };
  }
  changeColor = (color) => {
    this.setState({ color });
  }
  render() {
    let contextValue = { color: this.state.color, changeColor: this.changeColor };
    return (
      <Provider value={contextValue}>
        <div style={{ ...style, width: '250px', border: `5px solid ${this.state.color}` }}>
          Page
          <Header />
          <Main />
        </div>
      </Provider >
    )
  }
}
let element = <Page />
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

10. 高阶组件 #

const NewComponent = higherOrderComponent(OldComponent)

10.1 属性代理 #

import React from 'react';
import ReactDOM from 'react-dom/client';
const loading = message =>OldComponent =>{
    return class extends React.Component{
        render(){
            const state = {
                show:()=>{
                   console.log('show', message);
                },
                hide:()=>{
                     console.log('hide', message);
                }
            }
            return  (
                <OldComponent {...this.props} {...state} {...{...this.props,...state}}/>
            )
        }
    }
}
class Hello extends React.Component{
  render(){
     return (
      <div>
        hello
        <button onClick={this.props.show}>show</button>
        <button onClick={this.props.hide}>hide</button>
      </div>
     );
  }
}
const LoadingHello  = loading('loading')(Hello);
const element = <LoadingHello/>;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

10.2 反向继承 #

import React from 'react';
import ReactDOM from 'react-dom/client';
class Button extends React.Component{
  state = {name:'张三'}
  componentWillMount(){
      console.log('Button componentWillMount');
  }
  componentDidMount(){
      console.log('Button componentDidMount');
  }
  render(){
      console.log('Button render');
      return <button name={this.state.name} title={this.props.title}/>
  }
}
const wrapper = OldComponent =>{
  return class NewComponent extends OldComponent{
      state = {number:0}
      componentWillMount(){
          console.log('WrapperButton componentWillMount');
           super.componentWillMount();
      }
      componentDidMount(){
          console.log('WrapperButton componentDidMount');
           super.componentDidMount();
      }
      handleClick = ()=>{
          this.setState({number:this.state.number+1});
      }
      render(){
          console.log('WrapperButton render');
          let renderElement = super.render();
          let newProps = {
              ...renderElement.props,
              ...this.state,
              onClick:this.handleClick
          }
          return  React.cloneElement(
              renderElement,
              newProps,
              this.state.number
          );
      }
  }
}
let WrappedButton = wrapper(Button);
const element = <WrappedButton/>;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

11. render props #

11.1 原生实现 #

import React from 'react';
import ReactDOM from 'react-dom/client';
class MouseTracker extends React.Component {
  constructor(props) {
      super(props);
      this.state = { x: 0, y: 0 };
  }

  handleMouseMove = (event) => {
      this.setState({
          x: event.clientX,
          y: event.clientY
      });
  }

  render() {
      return (
          <div onMouseMove={this.handleMouseMove}>
              <h1>移动鼠标!</h1>
              <p>当前的鼠标位置是 ({this.state.x}, {this.state.y})</p>
          </div>
      );
  }
}
const element = <MouseTracker />;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

11.2 children #

import React from 'react';
import ReactDOM from 'react-dom/client';
class MouseTracker extends React.Component {
  constructor(props) {
      super(props);
      this.state = { x: 0, y: 0 };
  }

  handleMouseMove = (event) => {
      this.setState({
          x: event.clientX,
          y: event.clientY
      });
  }

  render() {
      return (
          <div onMouseMove={this.handleMouseMove}>
              {this.props.children(this.state)}
          </div>
      );
  }
}
const element = <MouseTracker >
{
    (props) => (
        <div>
            <h1>移动鼠标!</h1>
            <p>当前的鼠标位置是 ({props.x}, {props.y})</p>
        </div>
    )
}
</MouseTracker >;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

11.3 render属性 #

import React from 'react';
import ReactDOM from 'react-dom/client';
class MouseTracker extends React.Component {
  constructor(props) {
      super(props);
      this.state = { x: 0, y: 0 };
  }

  handleMouseMove = (event) => {
      this.setState({
          x: event.clientX,
          y: event.clientY
      });
  }

  render() {
      return (
          <div onMouseMove={this.handleMouseMove}>
              {this.props.render(this.state)}
          </div>
      );
  }
}
const element = < MouseTracker render={params => (
  <>
      <h1>移动鼠标!</h1>
      <p>当前的鼠标位置是 ({params.x}, {params.y})</p>
  </>
)} />;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

11.4 HOC #

import React from 'react';
import ReactDOM from 'react-dom/client';
function withTracker(OldComponent){
  return class MouseTracker extends React.Component{
    constructor(props){
        super(props);
        this.state = {x:0,y:0};
    }
    handleMouseMove = (event)=>{
        this.setState({
            x:event.clientX,
            y:event.clientY
        });
    }
    render(){
        return (
            <div onMouseMove = {this.handleMouseMove}>
               <OldComponent {...this.state}/>
            </div>
        )
    }
 }
}
function Show(props){
    return (
        <React.Fragment>
          <h1>请移动鼠标</h1>
          <p>当前鼠标的位置是: x:{props.x} y:{props.y}</p>
        </React.Fragment>
    )
}
let HighShow = withTracker(Show);
const element = <HighShow/>;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

12.PureComponent #

在 React 中,PureComponent 是一种通过比较 state 和 props 的浅比较(shallow comparison)来决定是否更新组件的优化方式。通常情况下,如果组件的 state 或 props 发生改变,组件就会重新渲染。然而,有时候即使 state 或 props 改变了,但这个改变并不会影响到组件的渲染输出,此时重新渲染组件就会浪费资源。

使用 PureComponent 可以帮助我们避免这种无用的重新渲染。PureComponent 会覆盖 shouldComponentUpdate(),并在其中进行 props 和 state 的浅比较。如果 PureComponent 发现在浅比较下,props 和 state 都没有变化,那么它就会跳过该组件的更新,包括渲染其子组件、调用生命周期方法等。

请注意,PureComponent 进行的是浅比较,也就是说,它只比较了对象的顶层属性。如果对象的深层属性发生变化,PureComponent 可能无法准确地检测到这种变化。因此,当你在使用 PureComponent 时,需要保证 state 和 props 的变化是在顶层属性上进行的。

src\index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
class ClassCounter extends React.PureComponent {
  render() {
      console.log('ClassCounter render');
      return <div>ClassCounter:{this.props.count}</div>
  }
}
function FunctionCounter(props) {
  console.log('FunctionCounter render'); 
  return <div>FunctionCounter:{props.count}</div>
}
const MemoFunctionCounter = React.memo(FunctionCounter);
class App extends React.Component {
  state = { number: 0 }
  amountRef = React.createRef()
  handleClick = () => {
      let nextNumber = this.state.number + parseInt(this.amountRef.current.value);
      this.setState({ number: nextNumber });
  }
  render() {
      return (
          <div>
              <ClassCounter count={this.state.number} />
              <MemoFunctionCounter count={this.state.number} />
              <input ref={this.amountRef} />
              <button onClick={this.handleClick}>+</button>
          </div>
      )
  }
}
const element = <App/>;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

13.todoApp #

import React from 'react';
class AddTodo extends React.Component {
    state = { text: '' }
    handleChange = (event) => {
        this.setState({ text: event.target.value });
    }
    handleSubmit = (event) => {
        event.preventDefault();
        this.props.onAdd(this.state.text);
        this.setState({ text: '' });
    }
    render() {
        return (
            <form onSubmit={this.handleSubmit}>
                <input type="text" value={this.state.text} onChange={this.handleChange} />
                <button type='submit'>添加</button>
            </form>
        )
    }
}
class FilterTodo extends React.Component {
    handleChange = (event) => this.props.onFilterChange(event.target.value)
    render() {
        const { filter } = this.props;
        return (
            <>
                <input type="radio" value="all" name="filter" checked={filter === 'all'} onChange={this.handleChange} id="all" /><label htmlFor='all'>全部</label>
                <input type="radio" value="completed" name="filter" checked={filter === 'completed'} onChange={this.handleChange} id="completed" /><label htmlFor='completed'>已完成</label>
                <input type="radio" value="uncompleted" name="filter" checked={filter === 'uncompleted'} onChange={this.handleChange} id="uncompleted" /><label htmlFor='uncompleted'>未完成</label>
            </>
        )
    }
}
class TodoItem extends React.Component {
    render() {
        const { onToggle, todo: { text, completed } } = this.props;
        return (
            <li>
                <input type="checkbox" checked={completed} onChange={onToggle} />
                <span style={{ textDecoration: completed ? 'line-through' : 'none' }}>{text}</span>
            </li>
        )
    }
}
class TodoList extends React.Component {
    render() {
        return (
            <ul>
                {
                    this.props.todos.map((todo, index) => (
                        <TodoItem
                            key={index}
                            todo={todo}
                            onToggle={() => this.props.onToggle(index)} />
                    ))
                }
            </ul>
        )
    }
}
class TodoApp extends React.Component {
    state = { todos: [], filter: 'all' }
    handleAdd = (text) => {
        this.setState(prevState => ({
            todos: [...prevState.todos, { text, completed: false }]
        }));
    }
    handleToggle = (index) => {
        this.setState(prevState => {
            const todos = [...prevState.todos];
            todos[index].completed = !todos[index].completed;
            return todos;
        });
    }
    handleFilterChange = (filter) => {
        this.setState({ filter })
    }
    getFilterTodos = () => {
        let { todos, filter } = this.state;
        switch (filter) {
            case 'completed':
                return todos.filter(todo => todo.completed)
            case 'uncompleted':
                return todos.filter(todo => !todo.completed)
            case 'all':
                return todos;
        }
    }
    render() {
        return (
            <>
                <AddTodo onAdd={this.handleAdd} />
                <FilterTodo filter={this.state.filter} onFilterChange={this.handleFilterChange} />
                <TodoList todos={this.getFilterTodos()} onToggle={this.handleToggle} />
            </>
        )
    }
}
export default TodoApp;