本文适合零基础的同学、技术栈主Vue的同学快速入门 React
,对于有一定的 React
基础同学,相信看完也能有所收获。
在 React 的官方文档 首页有一行说明
理解为 React
是一个用于构建用户界面的 JavaScript
库
这里的 web and native
可以理解为 web
浏览器端,以及指定特定平台 ios
、android
、windows
等
React
的特点:
声明式编程:
Vue
、React
、Flutter
;React
可以根据最新的状态去渲染我们的 UI
界面组件化开发:
多平台适配:
React
发布之初主要是开发 Web
页面;Facebook
推出了 ReactNative
,用于开发移动端跨平台;(虽然目前 Flutter
非常火爆,但是还是有很多公司在使用 ReactNative
);ReactVR
,用于开发虚拟现实 Web
应用程序;(VR也会是一个火爆的应用场景);为了演练 React
,我们可以提出一个小的需求:
Hello World
Hello React
在这个案例中我们只是初体验一下 react
开发,如果在这个过程中你碰到有些不理解的内容,没关系跳过,我们后面会有详细的讲解。
当然,你也可以使用 jQuery
和 Vue
来实现,甚至是原生方式来实现,对它们分别进行对比学习
开发 React
必须依赖三个库:
react
:包含 react
所必须的核心代码react-dom
: react
渲染在不同平台所需要的核心代码babel
:将 jsx
转换成 React
代码的工具FQA
:
1. 你可能会问,我学习 Vue
只要依赖一个 vue.js
文件就可以了,为什么 React
要依赖这么多东西呢?
其实呢,这三个库是各司其职的,目的就是让每一个库只单纯做自己的事情,在 React
的 0.14
版本之前是没有 react-dom
这个概念的,所有功能都包含在 react
里。
2. 那么 React
为什么要进行拆分呢?
原因就是 react-native
。react
包中包含了 react web
和 react-native
所共同拥有的核心代码。
react-dom
针对 web
和 native
所完成的事情不同:
web
端: react-dom
会将 jsx
最终渲染成真实的 DOM
,显示在浏览器中native
端: react-dom
会将 jsx
最终渲染成原生的控件(比如Android中的Button,iOS中的UIButton)。3. 为什么还要引入 babel
呢?
babel
是目前前端使用非常广泛的编译器、转移器。比如当下很多浏览器并不支持 ES6
的语法,但是确实 ES6
的语法非常的简洁和方便,我们开发时希望使用它。那么编写源码时我们就可以使用 ES6
来编写,之后通过 Babel
工具,将ES6
转成大多数浏览器都支持的 ES5
的语法。
默认情况下开发 React
其实可以不使用 babel
。但是前提是我们自己使用 React.createElement
来编写源代码,它编写的代码非常的繁琐和可读性差。那么我们就可以直接编写 jsx
(JavaScript XML)的语法,并且让 babel
帮助我们转换成 React.createElement
。关于 jsx
后续还会详细讲到;
对于上面三个包的依赖,我们可以通过以下三种方式引入:
暂时我们下载到本地引入
react
包地址:https://unpkg.com/react@18/umd/react.development.jsreact-dom
包地址:https://unpkg.com/react-dom@18/umd/react-dom.development.jsbabel
包地址:https://unpkg.com/babel-standalone@6/babel.min.jsReact
显示一个 Hello World
html<div id="app"></div>
<script type="text/babel">
// 1. 定义变量
const message = "Hello World";
const root = ReactDOM.createRoot(document.querySelector("#app"));
// 2. 渲染内容
root.render(<h1>{message}</h1>);
</script>
ReactDOM.createRoot
函数:用于创建一个 React
根,之后渲染的内容会包含在这个根中。参数:将渲染的内容,挂载到哪一个 HTML
元素上root.render
函数:渲染内容。参数:要渲染的根组件{}
语法来引入外部的变量或者表达式修改文本
按钮,添加它的点击事件修改 message
变量html<div id="app"></div>
<script type="text/babel">
// 1. 定义变量
let message = "Hello World";
const handleClick = () => {
message = "Hello React";
console.log(message);
};
const root = ReactDOM.createRoot(document.querySelector("#app"));
// 2. 渲染内容
root.render(
<div>
<h1>{message}</h1>
<button onClick={handleClick}>改变文本</button>
</div>
);
</script>
上面的代码修改完之后我们可以在控制台中看到message 变成了 Hello React
但是界面上却依然是发现 Hello World
,这是为什么呢?
原因就是我们虽然已经修改了 message
,但是并没有重新渲染内容,正确的做法是在 handleClick
方法种再次调用 root.render
方法,但是这样子我们就重复调用了两次 root.render
方法了,所以我们可以将这段逻辑封装一下:
render
函数封装html<div id="app"></div>
<script type="text/babel">
// 1. 定义变量
let message = "Hello World";
const handleClick = () => {
message = "Hello React";
render();
};
const root = ReactDOM.createRoot(document.querySelector("#app"));
// 2. 渲染内容
render();
function render() {
root.render(
<div>
<h1>{message}</h1>
<button onClick={handleClick}>改变文本</button>
</div>
);
}
</script>
在上一节中我们可以将整个 render
函数逻辑其实可以看做一个整体,将其封装成一个组件:
root.render
参数是一个 HTML
元素或者一个组件;ReactDOM.render
函数中的第一个参数;那么,在 React
中,如何封装一个组件呢?这里我们暂时使用类的方式封装组件
React.Component
render
函数
render
当中返回的 jsx
内容,就是之后 React
会帮助我们渲染的内容html<div id="app"></div>
<script type="text/babel">
// 1. 定义根组件
class App extends React.Component {
render() {
return <h1>Hello World</h1>;
}
}
// 2. 渲染跟组件
const root = ReactDOM.createRoot(document.querySelector("#app"));
root.render(<App />);
</script>
1. 组件化问题一:数据在哪里定义?
在组件中的数据,我们可以分成两类:
参与界面更新的数据我们也可以称之为是参与数据流,这个数据是定义在当前对象的 state
中:
this.state = {定义的数据}
this.setState
来更新数据,它会通知 React
进行 update
操作。在进行 update
操作时,就会重新调用render
函数,并且使用最新的数据,来渲染界面。jsclass App extends React.Component {
constructor() {
super();
this.state = {
message: "Hello World",
};
}
render() {
return <h1>{this.state.message}</h1>;
}
}
2. 组件化问题二:事件绑定中的 this
在类中直接定义一个函数,并且将这个函数绑定到元素的 onClick
事件上,当前这个函数的 this
指向的是谁呢?
默认情况下是 undefined
,因为在正常的 DOM
操作中,监听点击,监听函数中的 this
其实是节点对象(比如说是button对象)。但是因为 React
并不是直接渲染成真实的 DOM
,我们所编写的 button
只是一个语法糖,它的本质 是React
的 Element
对象; React
在执行函数时并没有绑定 this
,所以默认情况下就是一个 undefined
;
我们在绑定的函数中,可能想要使用当前对象,比如执行 this.setState
函数,就必须拿到当前对象的 this
,我们就需要在传入函数时,给这个函数直接绑定this
,类似于下面的写法
好了,至此我们这个案例就已经完成了。
js// 1. 定义根组件
const element = <h1>hello jsx</h1>;
// 2. 渲染跟组件
const root = ReactDOM.createRoot(document.querySelector("#app"));
root.render(element);
这段 element
变量的声明右侧赋值的标签语法是什么呢?
HTML
元素,但是我们能在 js
中直接给一个变量赋值 html
吗?type="text/babel"
去除掉,那么就会出现语法错误;JSX
是什么?
JSX
是一种 JavaScript
的语法扩展( extension
),也在很多地方称之为 JavaScript XML
,因为看起就是一段 XML
语法;UI
界面,并且其完成可以和 JavaScript
融合在一起使用;Vue
中的模块语法,你不需要专门学习模块语法中的一些指令(比如 v-for
、v-if
、v-else
、v-bind
);React
认为渲染逻辑本质上与其他 UI
逻辑存在内在耦合
UI
需要绑定事件(button、a原生等等);UI
中需要展示数据状态;他们之间是密不可分,所以 React
没有将标记分离到不同的文件中,而是将它们组合到了一起,这个地方就是组件(Component);
在这里,我们只需要知道,JSX
其实是嵌入到 JavaScript
中的一种结构语法;
JSX
的书写规范
JSX
的顶层只能有一个根元素,所以我们很多时候会在外层包裹一个 div
元素(或者使用后面我们学习的 Fragment
);jsx
的外层包裹一个小括号(),这样可以方便阅读,并且 jsx
可以进行换行书写;JSX
中的标签可以是单标签,也可以是双标签;注意:如果是单标签,必须以
/>
结尾;
jsx
中的注释jsx
中的注释要在一个大括号的包裹下书写
JSX
嵌入变量作为子元素Number
、String
、Array
类型时,可以直接显示null
、undefined
、boolean
类型时,内容为空;Object
对象类型不能作为子元素(not valid as a React child
)JSX
嵌入表达式DOM
原生有一个监听事件,我们可以如何操作呢?
DOM
原生,添加监听事件;HTML
原生中,直接绑定 onclick
;React
中是如何操作呢?我们来实现一下 React
中的事件监听,这里主要有两点不同
React
事件的命名采用小驼峰式(camelCase
),而不是纯小写;{}
传入一个事件处理函数,这个函数会在事件发生时被执行;问题一:this
的绑定问题?
在上一章我们也提到了 this
默认指向的是 undefined
handleClick
函数并不是我们主动调用的,而且当 button
发生改变时,React
内部调用了 btnClick
函数;this
;解决 this
的问题有以下三种方法:
bind
改变 this
指向bind
改变 this
指向问题二:事件参数传递问题?
在执行事件函数时,有可能我们需要获取一些参数信息:比如 event对
象、其他参数
event
对象
event
对象来做一些事情(比如阻止默认行为)event
对象有被直接传入,函数就可以获取到 event
对象;js class App extends React.Component {
btnClick(e, name, age) {
console.log(this, event, name, age);
}
render() {
return (
<div>
<button onClick={(e) => this.btnClick(e, "coder", 18)}>按钮</button>
</div>
);
}
}
Vue
中,我们会通过指令来控制:比如 v-if
、v-show
;React
中,所有的条件判断都和普通的 JavaScript
代码一致;v-show
的效果
display
属性是否为 none
React
中并没有像 Vue
模块语法中的 v-for
指令,而且需要我们通过 JavaScript
代码的方式组织数据,转成 JSX
:
Vue
转型到 React
的同学非常不习惯,认为 Vue
的方式更加的简洁明了;React
中的 JSX
正是因为和 JavaScript
无缝的衔接,让它可以更加的灵活;React
是真正可以提高我们编写代码能力的一种方式;React
中,展示列表最多的方式就是使用数组的 map
高阶函数;filter
函数slice
函数列表中的 key
我们会发现在前面的代码中,在 li
标签上添加了一个 key
,如果没有添加,React
会报下面的警告
这个警告是告诉我们需要在列表展示的 jsx
中添加一个 key
。
key
主要的作用是为了提高 diff
算法时的效率;jsx
仅仅只是 React.createElement
(component
, props
, ...children
) 函数的语法糖。
jsx
最终都会被转换成 React.createElement
的函数调用。createElement
需要传递三个参数:type
ReactElement
的类型;config
children
我们知道默认 jsx
是通过 babel
帮我们进行语法转换的,所以我们之前写的jsx
代码都需要依赖 babel
。
拿到 babel
编译的代码后,我们不再需要引入 babel
依赖
我们通过 React.createElement
最终创建出来一个 ReactElement
对象:
ReactElement
对象是什么作用呢? React
为什么要创建它呢?React
利用 ReactElement
对象组成了一个 JavaScript
的对象树;JavaScript
的对象树就是虚拟DOM(Virtual DOM);ReactElement
的树结构呢?
jsx
返回结果进行打印;jsx
的打印;ReactElement
最终形成的树结构就是 Virtual DOM
;虚拟DOM帮助我们从命令式编程转到了声明式编程的模式
React
官方的说法: Virtual DOM 是一种编程理念。
UI
以一种理想化或者说虚拟化的方式保存在内存中,并且它是一个相对简单的 JavaScript
对象ReactDOM.render
让 虚拟DOM 和 真实DOM 同步起来,这个过程中叫做协调(Reconciliation);React
声明式的 API
:
React
希望让 UI
是什么状态;React
来确保 DOM
和这些状态是匹配的;DOM
操作,就可以从手动更改 DOM
、属性操作、事件处理中解放出来;如果我们只是开发几个小的 demo
程序,那么永远不需要考虑一些复杂的问题
现代的前端项目已经越来越复杂了,不会再是在 HTML
中引入几个 css
文件,引入几个编写的 js
文件或者第三方的 js
文件这么简单
css
可能是使用 less
、sass
等预处理器进行编写,我们需要将它们转成普通的 css
才能被浏览器解析JavaScript
代码不再只是编写在几个文件中,而是通过模块化的方式,被组成在成百上千个文件中,我们需要通过模块化的技术来管理它们之间的相互依赖。为了解决上面这些问题,我们需要再去学习一些工具,比如 babel
、webpack
、gulp
,配置它们转换规则、打包依赖、热更新等等一些的内容,脚手架的出现,就是帮助我们解决这一系列问题的;
传统的脚手架指的是建筑学的一种结构:在搭建楼房、建筑物时,临时搭建出来的一个框架;
编程中提到的脚手架(Scaffold),其实是一种工具,帮我们可以快速生成项目的工程化结构;
总结:脚手架让项目从搭建到开发,再到部署,整个流程变得快速和便捷;
对于现在比较流行的三大框架都有属于自己的脚手架:
它们的作用都是帮助我们生成一个通用的目录结构,并且已经将我们所需的工程环境配置好。
使用这些脚手架需要依赖什么呢?
这里推荐大家下载LTS(Long-term support )版本,是长期支持版本,会比较稳定;
现在,我们就可以通过脚手架来创建React项目了。
创建React项目的命令如下:
创建完成后,进入对应的目录,就可以将项目跑起来:
create-react-app 项目名称 cd 01-test-react yarn start
我们可以通过VSCode打开项目:
整个目录结构都非常好理解,只是有一个PWA相关的概念:
PWA解决了哪些问题呢?
更多PWA相关的知识,可以自行去学习更多;
组件化是一种分而治之的思想
React
的组件相对于 Vue
更加的灵活和多样,按照不同的方式可以分成很多类组件
当然还有很多组件的其他概念:比如异步组件、高阶组件等等...
类组件的定义有如下要求:
在 ES6
之前,可以通过 create-react-class
模块来定义类组件,但是目前官网建议我们使用 ES6
的class
类定义。
使用 class
定义一个组件:
constructor
是可选的,我们通常在 constructor
中初始化一些数据;this.state
中维护的就是我们组件内部的数据;render()
方法是 class
组件中唯一必须实现的方法;render
被调用时,它会检查 this.props
和 this.state
的变化并返回以下类型之一:React
元素:
JSX
创建。<div />
会被 React
渲染为 DOM
节点,<MyComponent />
会被 React
渲染为自定义组件;<div />
还是 <MyComponent />
均为 React
元素。fragments
:使得 render
方法可以返回多个元素。Portals
:可以渲染子节点到不同的 DOM
子树中。DOM
中会被渲染为文本节点null
:什么都不渲染。函数组件是使用 function
来进行定义的函数,只是这个函数会返回和类组件中 render
函数返回一样的内容。
函数组件有自己的特点(当然,后面我们会讲 hooks
,就不一样了):
this
关键字不能指向组件实例(因为没有组件实例);state
);我们来定义一个函数组件:
在前面的学习中,我们主要讲解类组件,后面学习 Hooks
时,会针对函数式组件进行更多的学习。
生命周期是一个抽象的概念,在生命周期的整个过程,分成了很多个阶段;
React
内部为了告诉我们当前处于哪些阶段,会对我们组件内部实现的某些函数进行回调,这些函数就是生命周期函数:
componentDidMount
函数:组件已经挂载到 DOM
上时,就会回调;componentDidUpdate
函数:组件已经发生了更新时,就会回调;componentWillUnmount
函数:组件即将被移除时,就会回调;我们谈 React
生命周期时,主要谈的类的生命周期,因为函数式组件是没有生命周期函数的;(后面我们可以通过 hooks
来模拟一些生命周期的回调)
我们先来学习一下最基础、最常用的生命周期函数:
如果不初始化 state
或不进行方法绑定,则不需要为 React
组件实现构造函数。
constructor
中通常只做两件事情:
this.state
赋值对象来初始化内部的 state
;componentDidMount()
会在组件挂载后(插入 DOM
树中)立即调用。componentDidMount
中通常进行哪里操作呢?
DOM
的操作可以在这里进行;componentWillUnmount
取消订阅)componentDidUpdate()
会在更新后会被立即调用,首次渲染不会执行此方法。
DOM
进行操作;props
进行了比较,也可以选择在此处进行网络请求;(例如,当 props 未发生变化时,则不会执行网
络请求)。componentWillUnmount()
会在组件卸载及销毁之前直接调用。
timer
,取消网络请求或清除在 componentDidMount()
中创建的订阅等;除了上面介绍的生命周期函数之外,还有一些不常用的生命周期函数:
getDerivedStateFromProps
:state
的值在任何时候都依赖于 props
时使用;该方法返回一个对象来更新state;getSnapshotBeforeUpdate
:在 React
更新 DOM
之前回调的一个函数,可以获取 DOM
更新前的一些信息(比如说滚动位置);shouldComponentUpdate
:该生命周期函数很常用,但是我们等待讲性能优化时再来详细讲解;React
中还提供了一些过期的生命周期函数,这些函数已经不推荐使用。在开发过程中,我们会经常遇到需要组件之间相互进行通信:
比如 App
可能使用了多个 Header
,每个地方的 Header
展示的内容不同,那么我们就需要使用者传递给 Header
一些数据,让其进行展示;
也可能是子组件中发生了事件,需要由父组件来完成某些操作,那就需要子组件向父组件传递事件;
总之,在一个 React
项目中,组件之间的通信是非常重要的环节
父组件在展示子组件,可能会传递一些数据给子组件:
props
参数获取父组件传递过来的数据;jsimport React, { Component } from "react";
class ChildCpn extends Component {
constructor(props) {
super();
this.props = props;
}
render() {
const { name, age } = this.props;
return (
<div>
<h2>我是class的组件</h2>
<p>展示父组件传递过来的数据:{name + "" + age}</p>
</div>
);
}
}
class App extends Component {
render() {
return (
<div>
<ChildCpn name="coder" age="25" />
</div>
);
}
}
export default App;
jsfunction ChildCpn(props) {
const { name, age } = props;
return (
<div>
<h2>我是class的组件</h2>
<p>展示父组件传递过来的数据:{name + "" + age}</p>
</div>
);
}
class App extends Component {
render() {
return (
<div>
<ChildCpn name="coder" age="25" />
</div>
);
}
}
对于传递给子组件的数据,有时候我们可能希望进行验证,特别是对于大型项目来说:
Flow
或者 TypeScript
,那么直接就可以进行类型验证;Flow
或者 TypeScript
,也可以通过 prop-types
库来进行参数验证;从 React v15.5
开始,React.PropTypes
已移入另一个包中:prop-types
库
jsimport PropTypes from 'prop-types';
class ChildCpn extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
ChildCpn.propTypes = {
name: PropTypes.string
};
更多的验证方式,可以参考官网:https://zh-hans.reactjs.org/docs/typechecking-with-proptypes.html
key
以及 value
是什么类型;requiredFunc
: PropTypes.func.isRequired
如果我们没有传递,可以使用 defaultProps
设置默认值
jsChildCpn.defaultProps = {
name: 'coder'
};
某些情况,我们也需要子组件向父组件传递消息:
Vue
中是通过自定义事件来完成的;React
中同样是通过 props
传递消息,只是让父组件给子组件传递一个回调函数,在子组件中调用这个函数即可;例如,假设我们有一个父组件 Parent
和一个子组件 Child
,Parent
中包含一个按钮和一个 Child
组件。当用户点击按钮时,我们希望 Child
组件能够向 Parent
组件发送一条消息。
下面是一个实现的例子:
js// Parent.js
import React, { Component } from "react";
import Child from "./Child";
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
message: ""
};
this.handleMessage = this.handleMessage.bind(this);
}
handleMessage(msg) {
this.setState({
message: msg
});
}
render() {
return (
<div>
<button onClick={() => this.handleMessage("Hello from Child!")}>
Send message to Child
</button>
<Child onMessage={this.handleMessage} />
<p>Received message from Child: {this.state.message}</p>
</div>
);
}
}
export default Parent;
// Child.js
import React, { Component } from "react";
class Child extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.props.onMessage("Hello from Child!");
}
render() {
return <button onClick={this.handleClick}>Send message to Parent</button>;
}
}
export default Child;
在这个例子中,我们使用了类组件来定义了父组件 Parent
和子组件 Child
。在 Parent
中,我们定义了一个名为 handleMessage
的函数,用于接收从子组件 Child
发送的消息。我们将这个函数作为 onMessage
属性传递给 Child
组件。
在子组件 Child
中,我们在点击按钮时调用 props.onMessage
函数,并将消息作为参数传递给它。当用户点击按钮时,Parent
中的 handleMessage
函数被调用,并将接收到的消息存储在 message
状态中。我们最后在 Parent
中展示了接收到的消息。
这样,我们就实现了子组件向父组件通信的功能。
在 Vue
当中有一个固定的做法是通过 slot
来完成的插槽的,而在 React
对于需要插槽的情况非常灵活,有两种方案可以实现:
每个组件都可以获取到 props.children
:它包含组件的开始标签和结束标签之间的内容。
组件的
children` 子元素;jsclass NavBar extends Component {
render(){
const { children } = this.props
reutrn (
<div className="nav-bar">
<div className="left">
{children[0]}
</div>
<div className="center">
{children[1]}
</div>
<div className="right">
{children[2]}
</div>
</div>
)
}
}
js<NavBar>
<button>按钮</button>
<h2>我是标题</h2>
<span>span</span>
</NavBar>
props
属性传递 React
元素;通过 children
实现的方案虽然可行,但是有一个弊端:通过索引值获取传入的元素很容易出错,不能精准的获取传入的原生;
另外一个种方案就是使用 props
实现:通过具体的属性名,可以让我们在传入和获取时更加的精准;
jsclass NavBar extends Component {
render(){
const { leftSlot, centerSlot, rightSlot } = this.props
reutrn (
<div className="nav-bar">
<div className="left">
{leftSlot}
</div>
<div className="center">
{centerSlot}
</div>
<div className="right">
{rightSlot}
</div>
</div>
)
}
}
js<NavBar
leftSlot={<button>按钮</button>}
centerSlot={<h2>我是标题</h2>}
rightSlot={<span>span</span>}
/>
非父子组件数据的共享:
props
属性自上而下(由父到子)进行传递。App
中定义这些信息,之后一层层传递下去,那么对于一些中间层不需要数据的组件来说,是一种冗余的操作。但是,如果层级更多的话,一层层传递是非常麻烦,并且代码是非常冗余的:
React
提供了一个API:Context;Context
提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props
;Context
设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言;Context
相关 API
jsconst MyContext = React.createContext(defaultValue)
js<MyContext.Provider value={/*某个值*/}>
class
上的 contextType
属性会被重赋值为一个由 React.createContext()
创建的 Context
对象:this.context
来消费最近 Context
上的那个值;render
函数中;jsMyClass.contextType = MyContext
React
组件也可以订阅到 context
变更。这能让你在 函数式组件 中完成订阅 context
。context
值,返回一个 React
节点;js<MyContext.Consumer>
{ value => /*基于 context 值进行渲染*/}
</MyContext.Consumer>
开发中我们并不能直接通过修改state的值来让界面发生更新:
state
之后,希望 React
根据最新的 State
来重新渲染界面,但是这种方式的修改React
并不知道数据发生了变
化;React
并没有实现类似于 Vue2
中的 Object.defineProperty
或者 Vue3
中的 Proxy
的方式来监听数据的变化;setState
来告知 React
数据已经发生了变化;疑惑:在组件中并没有实现 setState
的方法,为什么可以调用呢?
setState
方法是从 Component
中继承过来的setState
的更新是异步的吗?
jschangeText() {
this.setState({
message: 'Hello World'
})
console.log(this.state.message)
}
上面的代码最终打印结果并不是是 Hello World
。可见 setState
是异步的操作,我们并不能在执行完setState
之后立马拿到最新的 state
的结果
为什么 setState
设计为异步呢?
因为 setState
设计为异步,可以显著的提升性能;
setState
都进行一次更新,那么意味着 render
函数会被频繁调用,界面重新渲染,这样效率是很低的;如果同步更新了 state
,但是还没有执行 render
函数,那么 state
和 props
不能保持同步。 state
和 props
不能保持一致性,会在开发中产生很多的问题;
setState
一定是异步吗?
其实要看情况
在 React18
之前分两种情况:
React
合成事件中,setState
是异步;setTimeout
或者原生 dom
事件中,setState
是同步;在 React18
之后,默认所有的操作都被放到了批处理中(异步处理)。
如果希望代码可以同步会拿到,则需要执行特殊的 flushSync
操作:
jsflushSync(() => {
this.setState({ counter: 8888 })
})
console.log(this.state.counter)
方式一:setState
的回调
setState
接受两个参数:第二个参数是一个回调函数,这个回调函数会在更新后会执行;
格式如下:setState(partialState, callback)
jschangeText() {
this.setState({
message: 'Hello World'
}, () => {
console.log(this.state.message)
})
}
方式二: 可以在生命周期函数中获取
jscomponentDidUpdate(prevProps, provState, snapShot){
console.log(this.state.message)
}
React
给我们提供了一个生命周期方法 shouldComponentUpdate
(很多时候,我们简称为SCU),这个方法接受参数,并且需要有返回值
该方法有两个参数:
nextProps
修改之后,最新的 props
属性nextState
修改之后,最新的 state
属性该方法返回值是一个 boolean
类型:
true
,那么就需要调用 render
方法;false
,那么久不需要调用 render
方法;true
,也就是只要 state
发生改变,就会调用 render
方法;比如我们在 App
中增加一个 message
属性:
jsx
中并没有依赖这个 message
,那么它的改变不应该引起重新渲染;render
监听到 state
的改变,就会重新 render
,所以最后 render
方法还是被重新调用了;如果所有的类,我们都需要手动来实现 shouldComponentUpdate
,那么会给我们开发者增加非常多的工作量。
shouldComponentUpdate
中的各种判断的目的是什么?props
或者 state
中的数据是否发生了改变,来决定 shouldComponentUpdate
返回 true
或者false
;事实上 React
已经考虑到了这一点,所以 React
已经默认帮我们实现好了,如何实现呢?
那就是将 class
继承自 PureComponent
即可
针对类组件可以使用 PureComponent
,而函数组件可以使用 高阶组件 React.memo()
来实现
在React的开发模式中,通常情况下不需要、也不建议直接操作DOM原生,但是某些特殊的情况,确实需要获取到DOM进行某些操作:
如何创建refs来获取对应的DOM呢?目前有三种方式:
使用时通过 this.refs.传入的字符串格式获取对应的元素;
对象是通过 React.createRef() 方式创建出来的;
使用时获取到创建的对象其中有一个current属性就是对应的元素;
该函数会在DOM被挂载时进行回调,这个函数会传入一个 元素对象,我们可以自己保存;
使用时,直接拿到之前保存的元素对象即可;
本文作者:叶继伟
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!