React是Facebook开发并在2013年开源的一款JavaScript库,用于构建用户界面,尤其是单页应用。它允许开发者通过组合多个组件来创建复杂的UI。
组件化:React以组件为单位构建界面,这使得代码复用和维护变得简单且方便。每个组件都有自己的状态和逻辑,可以独立渲染并且可以轻松地嵌套在其他组件中。
声明式设计:React采用声明式编程模型,开发者只需要描述应用程序在任何特定状态下的UI应该呈现的样子,而无需担心如何从一个状态转换到另一个状态。React会自动管理UI的更新。
虚拟DOM:React引入了虚拟DOM的概念,当应用的状态改变时,React会创建一个新的虚拟DOM,与上一个虚拟DOM进行比较,然后只更新有差异的部分。这可以极大地提高应用的性能。
React生态:React拥有丰富的生态系统,有大量的开源库和工具可以使用,如Redux、React Router等。此外,社区活跃,你可以在遇到问题时得到快速的帮助。
React主要用于构建用户界面。由于它的灵活性,它可以用于开发各种应用,例如:
mkdir 1.react
cd 1.react
npm init -y
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 文件中。 |
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'
})
]
};
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");
src\index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>React App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
{
"scripts": {
"start": "webpack serve --mode development",
"build": "webpack --mode production"
}
}
在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!'。
在实际的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 中,你可以使用花括号 {}
来插入任意的 JavaScript 表达式。例如:
const name = 'John';
const element = <h1>Hello, {name}!</h1>;
属性:可以使用类似于 HTML 的方式来为 JSX 元素定义属性(或者 props)。但是注意,有些词是 JavaScript 的保留字,所以需要使用特定的 JSX 属性名,例如使用 className
而不是 class
。属性值可以是字符串也可以是 JavaScript 表达式。
const className = 'greeting';
const element = <h1 className={className}>Hello, World!</h1>;
子元素:JSX 标签可以包含子元素:
const element = (
<div>
<h1>Hello!</h1>
<p>Welcome to our website.</p>
</div>
);
这个 div
元素有两个子元素:一个 h1
元素和一个 p
元素。
总的来说,JSX 提供了一种更为直观和易读写的方式来定义 React 元素。虽然它并非必须使用,但是它极大地提升了 React 的开发体验。
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);
JavaScript
函数。它接受任意的入参(props属性),并返回用于描述页面展示内容的 React 元素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 />
)。
类组件是另一种定义组件的方式
类组件是使用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);
在 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.state
和 this.setState
来使用状态。
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
。
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);
在React中,ref(引用)是一种属性,可以用来访问并操作DOM元素或者类组件的实例。这通常在你需要从React的数据流之外去操作元素时才会用到。
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);
在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);
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);
React 组件的生命周期可以分为三个主要阶段:挂载阶段(Mounting)、更新阶段(Updating)和卸载阶段(Unmounting)。
下面是 React 类组件的生命周期方法:
当一个组件被插入到 DOM 中时,以下的方法将按照顺序被调用:
constructor()
static getDerivedStateFromProps()
render()
componentDidMount()
当组件的 props 或 state 发生改变时,将触发更新,以下的方法将按照顺序被调用:
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
当一个组件从 DOM 中被移除时,以下的方法将被调用:
componentWillUnmount()
以下是这些方法的简单解释:
constructor()
:这是组件的构造函数,最先被调用,常常在这里初始化组件的 state。
static getDerivedStateFromProps()
:当组件实例被创建和每次重新渲染时,都会调用 getDerivedStateFromProps()
。这是一个静态方法,用于将新的 props 值映射到组件的 state 中。
render()
:这是唯一必须的方法。当调用此方法时,它将检查 this.props
和 this.state
,然后返回一些 JSX。或者,你也可以返回 null 或 false 来表示什么都不渲染。
componentDidMount()
:在组件输出(也被称为 DOM)被渲染到页面后运行。这是一个好的地方去请求网络数据或设置订阅等操作。
shouldComponentUpdate()
:在重新渲染前调用,根据其返回值决定是否继续下一步的渲染过程。默认情况下,此方法返回 true
,通知 React 继续更新。
getSnapshotBeforeUpdate()
:在最新的渲染输出提交给 DOM 前将会立即调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。
componentDidUpdate()
:在组件完成更新后立即调用。在此方法中可以对 DOM 进行操作。
componentWillUnmount()
:在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清理 timer,取消网络请求或清除在 componentDidMount()
中创建的订阅等。
请注意,以上的生命周期方法适用于 React 16.3 及以上版本,其中废弃了 componentWillMount
,componentWillReceiveProps
和 componentWillUpdate
这些方法,而引入了新的生命周期方法,如 getDerivedStateFromProps
和 getSnapshotBeforeUpdate
。
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);
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);
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);
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);
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);
const NewComponent = higherOrderComponent(OldComponent)
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);
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);
render prop
是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术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);
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);
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);
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);
在 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);
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;