react相关汇总
# 基础
# 为什么选择使用框架而不是原生?
框架的好处:
- 组件化: 其中以 React 的组件化最为彻底,甚至可以到函数级别的原子组件,高度的组件化可以是我们的工程易于维护、易于组合拓展。
- 天然分层: JQuery 时代的代码大部分情况下是面条代码,耦合严重,现代框架不管是 MVC、MVP还是MVVM 模式都能帮助我们进行分层,代码解耦更易于读写。
- 生态: 现在主流前端框架都自带生态,不管是数据流管理架构还是 UI 库都有成熟的解决方案。
- 开发效率: 现代前端框架都默认自动更新DOM,而非我们手动操作,解放了开发者的手动DOM成本,提高开发效率,从根本上解决了UI 与状态同步问题.
# 什么是React?
- React 是 Facebook 在 2011 年开发的前端 JavaScript 库。
- 它遵循基于组件的方法,有助于构建可重用的UI组件。
- 它用于开发复杂和交互式的 Web 和移动 UI。
- 尽管它仅在 2015 年开源,但有一个很大的支持社区。
# React有什么特点?
React的主要功能如下:
- 它使用**虚拟DOM **而不是真正的DOM。
- 它可以用服务器端渲染。
- 它遵循单向数据流或数据绑定。
# 列出React的一些主要优点。
React的一些主要优点是:
- 它提高了应用的性能
- 可以方便地在客户端和服务器端使用
- 由于 JSX,代码的可读性很好
- React 很容易与 Meteor,Angular 等其他框架集成
- 使用React,编写UI测试用例变得非常容易
# React有哪些限制?
React的限制如下:
- React 只是一个库,而不是一个完整的框架
- 它的库非常庞大,需要时间来理解
- 新手程序员可能很难理解
- 编码变得复杂,因为它使用内联模板和 JSX
# React与Angular有何不同?
主题 | React | Angular |
---|---|---|
1. 体系结构 | 只有 MVC 中的 View | 完整的 MVC |
2. 渲染 | 可以在服务器端渲染 | 客户端渲染 |
3. DOM | 使用 virtual DOM | 使用 real DOM |
4. 数据绑定 | 单向数据绑定 | 双向数据绑定 |
5. 调试 | 编译时调试 | 运行时调试 |
6. 作者 |
# React 设计思路,它的理念是什么?
(1)编写简单直观的代码 React最大的价值不是高性能的虚拟DOM、封装的事件机制、服务器端渲染,而是声明式的直观的编码方式。翻开 react中文文档,第一条就是声明式,React 使创建交互式 UI 变得轻而易举。为你应用的每一个状态设计简洁的视图,当数据改变时 React 能有效地更新并正确地渲染组件。 以声明式编写 UI,可以让你的代码更加可靠,且方便调试。
(2)简化可复用的组件 React框架里面使用了简化的组件模型,但更彻底地使用了组件化的概念。React将整个UI上的每一个功能模块定义成组件,然后将小的组件通过组合或者嵌套的方式构成更大的组件。 React的组件具有如下的特性∶
- 可组合∶ 简单组件可以组合为复杂的组件
- 可重用∶每个组件都是独立的,可以被多个组件使用 o 可维护∶和组件相关的逻辑和UI都封装在了组件的内
部,方便维护
- 可测试∶ 因为组件的独立性,测试组件就变得方便很多。
(3) Virtual DOM 真实页面对应一个 DOM 树。在传统页面的开发模式中,每次需要更新页面时,都要手动操作 DOM 来进行更新。 DOM 操作非常昂贵。我们都知道在前端开发中,性能消耗最大的就是 DOM 操作,而且这部分代码会让整体项目的代码 变 得 难 以维护。React 把 真 实 DOM 树 转换 成 JavaScript 对象树,也就是 Virtual DOM,每次数据更新后,重新计算 Virtual DOM,并和上一次生成的 Virtual DOM 做对比,对发生变化的部分做批量更新。React 也提供了直观的 shouldComponentUpdate 生命周期回调,来减少数据变化后不必要的 Virtual DOM 对比过程,以保证性能。
(4)函数式编程 React 把过去不断重复构建 UI 的过程抽象成了组件,且在给定参数的情况下约定渲染对应的 UI 界面。React 能充分利用很多函数式方法去减少冗余代码。此外,由于它本身就是简单函数,所以易于测试。
(5)一次学习,随处编写 无论你现在正在使用什么技术栈,你都可以随时引入 React来开发新特性,而不需要重写现有代码。 React 还可以使用 Node 进行服务器渲染,或使用 React Native 开发原生移动应用。因为 React 组件可以映射为对应的原生控件。在输出的时候,是输出 Web DOM,还是 Android 控件,还是 iOS 控件,就由平台本身决定了。所以,react很方便和其他平台集成
# 对 React 和 Vue 的理解,它们的异同【要点】
(1)相似之处
- 都将注意力集中保持在核心库,而将其他功能如路由和全局状态管理交给相关的库
- 都有自己的构建工具,能让你得到一个根据最佳实践设置的项目模板。
- 都使用了Virtual DOM(虚拟DOM)提高重绘性能
- 都有props的概念,允许组件间的数据传递
- 都鼓励组件化应用,将应用分拆成一个个功能明确的模块,提高复用性
(2)不同之处
1)数据流 Vue默认支持数据双向绑定,而React一直提倡单向数据流
2)虚拟DOM Vue2.x开始引入"Virtual DOM",消除了和React在这方面的差异,但是在具体的细节还是有各自的特点。
- Vue宣称可以更快地计算出Virtual DOM的差异,这是由于它在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。
- 对于React而言,每当应用的状态被改变时,全部子组件都会重新渲染。当然,这可以通过 PureComponent/shouldComponentUpdate这个生命周期方法来进行控制,但Vue将此视为默认的优化。
3)组件化 React与Vue最大的不同是模板的编写。
- Vue鼓励写近似常规HTML的模板。写起来很接近标准 HTML元素,只是多了一些属性。
- React推荐你所有的模板通用JavaScript的语法扩展——JSX书写。
具体来讲:React中render函数是支持闭包特性的,所以我们import的组件在render中可以直接调用。但是在Vue中,由于模板中使用的数据都必须挂在 this 上进行一次中转,所以我们 import 一个组件完了之后,还需要在 components 中再声明下。
4)监听数据变化的实现原理不同
- Vue 通过 getter/setter 以及一些函数的劫持,能精确知道数据变化,不需要特别的优化就能达到很好的性能;
- React 默认是通过比较引用的方式进行的,如果不优化(PureComponent/shouldComponentUpdate)可能导致大量不必要的vDOM的重新渲染。这是因为 Vue 使用的是可变数据,而React更强调数据的不可变;
5)高阶组件 react可以通过高阶组件(Higher Order Components-- HOC)来扩展,而vue需要通过mixins来扩展。
原因高阶组件就是高阶函数,而React的组件本身就是纯粹的函数,所以高阶函数对React来说易如反掌。相反Vue.js使用HTML模板创建视图组件,这时模板无法有效的编译,因此Vue不采用HOC来实现。
6)构建工具 两者都有自己的构建工具
- React ==> Create React APP
- Vue ==> vue-cli
7)跨平台
- React ==> React Native
- Vue ==> Weex
# 在React中怎么使用async/await?
async/await是ES7标准中的新特性。如果是使用React官方的脚手架创建的项目,就可以直接使用。如果是在自己搭建的webpack配置的项目中使用,可能会遇到 regeneratorRuntime is not defined 的异常错误。那么我们就需要引入babel,并在babel中配置使用async/await。可以利用babel的 transform-async-to-module-method 插件来转换其成为浏览器支持的语法,虽然没有性能的提升,但对于代码编写体验要更好。
# 对React SSR的理解
服务端渲染是数据与模版组成的html,即 html= 数据 + 模版。将组件或页面通过服务器生成html字符串,再发送到浏览器,最后将静态标记"混合"为客户端上完全交互的应用程序。页面没使用服务渲染,当请求页面时,返回的body里为空,之后执行js将html结构注入到body里,结合css显示出来;
SSR的优势:
- 对SEO友好
- 所有的模版、图片等资源都存在服务器端
- 一个html返回所有数据
- 减少HTTP请求
- 响应快、用户体验好、首屏渲染快
1)更利于SEO 不同爬虫工作原理类似,只会爬取源码,不会执行网站的任何脚本使用了React或者其它MVVM框架之后,页面大多数DOM元素都是在客户端根据js动态生成,可供爬虫抓取分析的内容大大减少。另外,浏览器爬虫不会等待我们的数据完成之后再去抓取页面数据。服务端渲染返回给客户端的是已经获取了异步数据并执行JavaScript脚本的最终HTML,网络爬中就可以抓取到完整页面的信息。 2)更利于首屏渲染 首屏的渲染是node发送过来的html字符串,并不依赖于js文件了,这就会使用户更快的看到页面的内容。尤其是针对大型单页应用,打包后文件体积比较大,普通客户端渲染加载所有所需文件时间较长,首页就会有一个很长的白屏等待时间。
SSR的局限: 1)服务端压力较大 本来是通过客户端完成渲染,现在统一到服务端node服务去做。尤其是高并发访问的情况,会大量占用服务端CPU资源; 2)开发条件受限 在服务端渲染中,只会执行到componentDidMount之前的生命周期钩子,因此项目引用的第三方的库也不可用其它生命周期钩子,这对引用库的选择产生了很大的限制; 3)学习成本相对较高 除了对webpack、MVVM框架要熟悉,还需要掌握node、 Koa2等相关技术。相对于客户端渲染,项目构建、部署过程更加复杂。
时间耗时比较:
1)数据请求
由服务端请求首屏数据,而不是客户端请求首屏数据,这是"快"的一个主要原因。服务端在内网进行请求,数据响应速度快。客户端在不同网络环境进行数据请求,且外网http请求开销大,导致时间差
- 客户端数据请求
- 服务端数据请求
2)html渲染 服务端渲染是先向后端服务器请求数据,然后生成完整首屏 html返回给浏览器;而客户端渲染是等js代码下载、加载、解析完成后再请求数据渲染,等待的过程页面是什么都没有的,就是用户看到的白屏。就是服务端渲染不需要等待js代码下载完成并请求数据,就可以返回一个已有完整数据的首屏页面。
- 非ssr html渲染
- ssr html渲染
# 与 ES5 相比,React 的 ES6 语法有何不同?
以下语法是 ES5 与 ES6 中的区别:
- require 与 import
// ES5
var React = require('react');
// ES6
import React from 'react';
2
3
4
5
- export 与 exports
// ES5
module.exports = Component;
// ES6
export default Component;
2
3
4
- component 和 function
// ES5
var MyComponent = React.createClass({
render: function() {
return
<h3>Hello Edureka!</h3>;
}
});
// ES6
class MyComponent extends React.Component {
render() {
return
<h3>Hello Edureka!</h3>;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- props
// ES5
var App = React.createClass({
propTypes: { name: React.PropTypes.string },
render: function() {
return
<h3>Hello, {this.props.name}!</h3>;
}
});
// ES6
class App extends React.Component {
render() {
return
<h3>Hello, {this.props.name}!</h3>;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- state
// ES5
var App = React.createClass({
getInitialState: function() {
return { name: 'world' };
},
render: function() {
return
<h3>Hello, {this.state.name}!</h3>;
}
});
// ES6
class App extends React.Component {
constructor() {
super();
this.state = { name: 'world' };
}
render() {
return
<h3>Hello, {this.state.name}!</h3>;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# React的状态提升是什么?使用场景有哪些?
React的状态提升就是用户对子组件操作,子组件不改变自己的状态,通过自己的props把这个操作改变的数据传递给父组件,改变父组件的状态,从而改变受父组件控制的所有子组件的状态,这也是React单项数据流的特性决定的。官方的原话是:共享 state(状态) 是通过将其移动到需要它的组件的最接近的共同祖先组件来实现的。 这被称为“状态提升(Lifting State Up)”。
概括来说就是将多个组件需要共享的状态提升到它们最近的父组件上,在父组件上改变这个状态然后通过props分发给子组件。
一个简单的例子,父组件中有两个input子组件,如果我们想在第一个输入框输入数据,来改变第二个输入框的值,这就需要用到状态提升。
class Father extends React.Component {
constructor(props) {
super(props)
this.state = {
Value1: '',
Value2: ''
}
}
value1Change(aa) {
this.setState({
Value1: aa
})
}
value2Change(bb) {
this.setState({
Value2: bb
})
}
render() {
return (
<div style={{ padding: "100px" }}>
<Child1 value1={this.state.Value1} onvalue1Change={this.value1Change.bind(this)} />
<Child2 value2={this.state.Value1} />
</div>
)
}
}
class Child1 extends React.Component {
constructor(props) {
super(props)
}
changeValue(e) {
this.props.onvalue1Change(e.target.value)
}
render() {
return (
<input value={this.props.Value1} onChange={this.changeValue.bind(this)} />
)
}
}
class Child2 extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<input value={this.props.value2} />
)
}
}
ReactDOM.render(
<Father />,
document.getElementById('root')
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 什么是状态提升
使用 react 经常会遇到几个组件需要共用状态数据的情况。这种情况下,最好将这部分共享的状态提升至他们最近的父组件当中进行管理。我们来看一下具体如何操作吧。
import React from 'react'
class Child_1 extends React.Component{
constructor(props){
super(props)
}
render(){
return (
<div>
<h1>{this.props.value+2}</h1>
</div>
)
}
}
class Child_2 extends React.Component{
constructor(props){
super(props)
}
render(){
return (
<div>
<h1>{this.props.value+1}</h1>
</div>
)
}
}
class Three extends React.Component {
constructor(props){
super(props)
this.state = {
txt:"牛逼"
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(e){
this.setState({
txt:e.target.value
})
}
render(){
return (
<div>
<input type="text" value={this.state.txt} onChange={this.handleChange}/>
<p>{this.state.txt}</p>
<Child_1 value={this.state.txt}/>
<Child_2 value={this.state.txt}/>
</div>
)
}
}
export default Three
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# 如何在React中使用innerHTML
增加dangerouslySetInnerHTML属性,并且传入对象的属性名叫_html
function Component(props){
return <div dangerouslySetInnerHTML={{_html:'<span>你好</span>'}}>
</div>
}
2
3
4
# 同时引用这三个库react.js、react-dom.js和babel.js它们都有什么作用?
- react:包含react所必须的核心代码
- react-dom:react渲染在不同平台所需要的核心代码
- babel:将jsx转换成React代码的工具
# 可以使用TypeScript写React应用吗?怎么操作?
(1)如果还未创建 Create React App 项目
- 直接创建一个具有 typescript 的 Create React App 项目:
npx create-react-app demo --typescript
(2)如果已经创建了 Create React App 项目,需要将 typescript 引入到已有项目中
- 通过命令将 typescript 引入项目:
npm install --save typescript @types/node @types/react @types/react-dom @types/jest
- 将项目中任何 后缀名为 ‘.js’ 的 JavaScript 文件重命名为 TypeScript 文件即后缀名为 ‘.tsx’(例如 src/index.js 重命名为 src/index.tsx )
# React的严格模式如何使用,有什么用处?
StrictMode
是一个用来突出显示应用程序中潜在问题的工具。与 Fragment
一样,StrictMode
不会渲染任何可见的 UI。它为其后代元素触发额外的检查和警告。 可以为应用程序的任何部分启用严格模式。例如:
import React from 'react';
function ExampleApplication() {
return (
<div>
<Header />
<React.StrictMode>
<div>
<ComponentOne />
<ComponentTwo />
</div>
</React.StrictMode>
<Footer />
</div>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
在上述的示例中,_不_会对 Header
和 Footer
组件运行严格模式检查。但是,ComponentOne
和 ComponentTwo
以及它们的所有后代元素都将进行检查。
StrictMode
目前有助于:
- 识别不安全的生命周期
- 关于使用过时字符串 ref API 的警告
- 关于使用废弃的 findDOMNode 方法的警告
- 检测意外的副作用
- 检测过时的 context API
# React 中的StrictMode(严格模式)是什么??
React 的StrictMode
是一种辅助组件,可以帮助咱们编写更好的 react 组件,可以使用<StrictMode />
包装一组组件,并且可以帮咱们以下检查:
- 验证内部组件是否遵循某些推荐做法,如果没有,会在控制台给出警告。
- 验证是否使用的已经废弃的方法,如果有,会在控制台给出警告。
- 通过识别潜在的风险预防一些副作用。
# 数据驱动视图(MVVM, setState)
- 数据驱动视图 - React
this.setState()
- 函数式编程:函数式式编程是一种编程范式,两个最重要的概念是 纯函数、不可变值
# 样式
# 样式化 React 应用程序的最常见方法是什么?
有多种方法可用于对 React 组件进行样式设置,每种方法各有利弊。要提到的主要是:
- 内联样式:适用于原型设计,但有局限性(例如,无伪类样式)
- 基于类的CSS样式:比内联样式更高效,并且对 React 刚接触的开发人员来说是熟悉的
- JS样式中的CSS:有许多库允许将样式在组件内声明为 JavaScript,从而将样式更像代码。
react-components-styling-options (opens new window)
- inline CSS
- normal CSS
- CSS in JS libraries
- CSS Modules
- Sass & SCSS
- Less
- Stylable
# 前端富文本 dangerouslySetInnerHTML
const rawHtml = '<div><p>Title</p></div>'
const rawHtmlData = {
__html: rawHtml // 这里有个下划线
}
return <div dangerouslySetInnerHTML={rawHtmlData}></div>
2
3
4
5
# JSX
# 什么是JSX?
JSX 是JavaScript XML 的简写。是 React 使用的一种文件,它利用 JavaScript 的表现力和类似 HTML 的模板语法。这使得 HTML 文件非常容易理解。此文件能使应用非常可靠,并能够提高其性能。下面是JSX的一个例子:
render(){
return(
<div>
<h1> Hello World from Edureka!!</h1>
</div>
);
}
2
3
4
5
6
7
JSX 是 JavaScript 语法的扩展,它允许编写类似于 HTML 的代码。它可以编译为常规的 JavaScript 函数调用,从而为创建组件标记提供了一种更好的方法。
JSX代码如下:
<div className="sidebar" />
它转换为以下JS代码:
React.createElement(
'div',
{className: 'sidebar'}
)
2
3
4
# 为什么浏览器无法读取JSX?
浏览器只能处理 JavaScript 对象,而不能读取常规 JavaScript 对象中的 JSX。所以为了使浏览器能够读取 JSX,首先,需要用像 Babel 这样的 JSX 转换器将 JSX 文件转换为 JavaScript 对象,然后再将其传给浏览器。
# 讲讲什么是 JSX ?
当 Facebook 第一次发布 React 时,他们还引入了一种新的 JS 方言 JSX
,将原始 HTML 模板嵌入到 JS 代码中。JSX 代码本身不能被浏览器读取,必须使用Babel
和webpack
等工具将其转换为传统的JS。很多开发人员就能无意识使用 JSX,因为它已经与 React 结合在一直了。
class MyComponent extends React.Component {
render() {
let props = this.props;
return (
<div className="my-component">
<a href={props.url}>{props.name}</a>
</div>
);
}
}
2
3
4
5
6
7
8
9
10
# 为什么 React 要用 JSX?
JSX 是一个 JavaScript 的语法扩展,或者说是一个类似于 XML 的 ECMAScript 语法扩展。它本身没有太多的语法定义,也不期望引入更多的标准。
其实 React 本身并不强制使用 JSX。在没有 JSX 的时候,React 实现一个组件依赖于使用 React.createElement 函数。代码如下:
class Hello extends React.Component {
render() {
return React.createElement(
'div',
null,
`Hello ${this.props.toWhat}`
);
}
}
ReactDOM.render(
React.createElement(Hello, {toWhat: 'World'}, null),
document.getElementById('root')
);
2
3
4
5
6
7
8
9
10
11
12
13
而 JSX 更像是一种语法糖,通过类似 XML 的描述方式,描写函数对象。在采用 JSX 之后,这段代码会这样写:
class Hello extends React.Component {
render() {
return <div>Hello {this.props.toWhat}</div>;
}
}
ReactDOM.render(
<Hello toWhat="World" />,
document.getElementById('root')
);
2
3
4
5
6
7
8
9
通过对比,可以清晰地发现,代码变得更为简洁,而且代码结构层次更为清晰。
因为 React 需要将组件转化为虚拟 DOM 树,所以在编写代码时,实际上是在手写一棵结构树。而XML 在树结构的描述上天生具有可读性强的优势。
但这样可读性强的代码仅仅是给写程序的同学看的,实际上在运行的时候,会使用 Babel 插件将 JSX 语法的代码还原为 React.createElement 的代码。
总结: JSX 是一个 JavaScript 的语法扩展,结构类似 XML。JSX 主要用于声明 React 元素,但 React 中并不强制使用 JSX。即使使用了 JSX,也会在构建过程中,通过 Babel 插件编译为 React.createElement。所以 JSX 更像是 React.createElement 的一种语法糖。
React 团队并不想引入 JavaScript 本身以外的开发体系。而是希望通过合理的关注点分离保持组件开发的纯粹性。
# React必须使用JSX吗?
React 并不强制要求使用 JSX。当不想在构建环境中配置有关 JSX 编译时,不在 React 中使用 JSX 会更加方便。
每个 JSX 元素只是调用 React.createElement(component, props, ...children)
的语法糖。因此,使用 JSX 可以完成的任何事情都可以通过纯 JavaScript 完成。
例如,用 JSX 编写的代码:
class Hello extends React.Component {
render() {
return <div>Hello {this.props.toWhat}</div>;
}
}
ReactDOM.render(
<Hello toWhat="World" />,
document.getElementById('root')
);
2
3
4
5
6
7
8
9
可以编写为不使用 JSX 的代码:
class Hello extends React.Component {
render() {
return React.createElement('div', null, `Hello ${this.props.toWhat}`);
}
}
ReactDOM.render(
React.createElement(Hello, {toWhat: 'World'}, null),
document.getElementById('root')
);
2
3
4
5
6
7
8
9
# 为什么使用jsx的组件中没有看到使用react却需要引入react?
本质上来说JSX是React.createElement(component, props, ...children)
方法的语法糖。在React 17之前,如果使用了JSX,其实就是在使用React, babel
会把组件转换为 CreateElement
形式。在React 17之后,就不再需要引入,因为 babel
已经可以帮我们自动引入react。
# 如何 React.createElement ?
const element = (
<h1 className="greeting">
Hello, world!
</h1>
)
2
3
4
5
上述代码如何使用 React.createElement
来实现:
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
2
3
4
5
# JSX 本质
JSX => React.createElement() => 虚拟DOM (JS对象) => 真实DOM
- JSX 等同于 Vue 模板
- Vue 模板不是 html
- JSX 也不是 JS
讲JSX语法,通过 React.createElement()
编译成Dom,BABEL 可以编译JSX
流程:JSX => React.createElement() => 虚拟DOM (JS对象) => 真实DOM
React 底层会通过 React.createElement()
这个方法,将 JSX 语法转成JS对象,React.createElement()
可以接收三个参数,第一个为标签名称,第二参数为属性,第三个参数为内容
createElement() 根据首字母大小写来区分是组件还是HTML标签,React规定组件首字母必须大写,HTML规定标签首字母必须小写
// 第一个参数为 标签(tag) 可为 'div'标签名 或 List组件
// 第二个参数为:属性(props)
// 第三个参数之后都为子节点(child),可以在第三个参数传一个数组,也可以在第三、四、五....参数中传入
React.createElement('tag', null, [child1, chlild2, child3])
或者
React.createElement('tag', { className: 'class1' }, child1, chlild2, child3)
2
3
4
5
6
# portals&Context上下文
# 什么是 React Context?
Context
通过组件树提供了一个传递数据的方法,从而避免了在每一个层级手动的传递 props
属性。
React 提供了上下文API,以解决应用内多个组件之间共享状态的问题。在引入上下文之前,唯一的选择是引入一个单独的状态管理库,例如 Redux。但是,许多开发人员认为 Redux 引入了许多不必要的复杂性,尤其是对于较小的应用程序。
# 什么是上下文Context
Context 通过组件树提供了一个传递数据的方法,从而避免了在每一个层级手动的传递 props 属性。
- 用法:在父组件上定义getChildContext方法,返回一个对象,然后它的子组件就可以通过this.context属性来获取
import React,{Component} from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
class Header extends Component{
render() {
return (
<div>
<Title/>
</div>
)
}
}
class Title extends Component{
static contextTypes={
color:PropTypes.string
}
render() {
return (
<div style={{color:this.context.color}}>
Title
</div>
)
}
}
class Main extends Component{
render() {
return (
<div>
<Content>
</Content>
</div>
)
}
}
class Content extends Component{
static contextTypes={
color: PropTypes.string,
changeColor:PropTypes.func
}
render() {
return (
<div style={{color:this.context.color}}>
Content
<button onClick={()=>this.context.changeColor('green')}>绿色</button>
<button onClick={()=>this.context.changeColor('orange')}>橙色</button>
</div>
)
}
}
class Page extends Component{
constructor() {
super();
this.state={color:'red'};
}
static childContextTypes = {
color: PropTypes.string,
changeColor:PropTypes.func
}
getChildContext() {
return {
color: this.state.color,
changeColor:(color)=>{
this.setState({color})
}
}
}
render() {
return (
<div>
<Header/>
<Main/>
</div>
)
}
}
ReactDOM.render(<Page/>,document.querySelector('#root'));
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# context :第二种方式Provider&Consumer
使用场景:公共信息(语言、主题)传递给每个组件,如果组件层级过多,用props
传递就会繁琐,用 redux
小题大做。
import React from 'react'
// 创建 Context 填入默认值(任何一个 js 变量)
export const {Provider,Consumer} = React.createContext("默认名称")
// 在最上级组件中
constructor (props) {
super(props)
this.state = { theme: 'light' }
}
render () {
// 这里使用 this.state.theme 是为了可以修改,初始化的值为默认值,不能修改
// value 中放共享的数据
return (
<Provider value={this.state.theme}>
....
<button onClick={this.changeTheme}></button>
</Provider>
)
}
// 子组件中调用
import { Consumer } from "./index";//引入父组件的Consumer容器
render () {
return (
// Consumer 容器,可以拿到上文传递下来的 theme 属性,并可以展示对应的值
<Consumer>
{ theme => <div>子组件。获取父组件的值: {theme} </div> }
</Consumer>
)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# react中的Portal是什么?
Portals 提供了一种很好的将子节点渲染到父组件以外的 DOM 节点的方式。
ReactDOM.createPortal(child, container)
第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或碎片。第二个参数(container)则是一个 DOM 元素。
# portals 传送门
使用 Portals 渲染到 body 上,fixed 元素要放在 body 上,有更好的浏览器兼容。
常见使用场景:
- 父组件 overflow: hidden , 但是子组件又想展示;
- 父组件的 z-index 太小;
- fixed 需要放在 body 第一层;
import ReactDOM from 'react-dom'
render () {
return ReactDOM.creatPortal(
<div>{this.props.children}</div>,
document.body
)
}
2
3
4
5
6
7
# 事件机制
# 请简单谈一下react的事件机制
- 当用户在为onClick添加函数时,React并没有将Click时间绑定在DOM上面。
- 而是在document处监听所有支持的事件,当事件发生并冒泡至document处时,React将事件内容封装交给中间层SyntheticEvent(负责所有事件合成)
- 所以当事件触发的时候,对使用统一的分发函数dispatchEvent将指定函数执行。
# 在 React 中如何处理事件
为了解决跨浏览器的兼容性问题,SyntheticEvent
实例将被传递给你的事件处理函数,SyntheticEvent
是 React 跨浏览器的浏览器原生事件包装器,它还拥有和浏览器原生事件相同的接口,包括 stopPropagation()
和 preventDefault()
。
比较有趣的是,React 实际上并不将事件附加到子节点本身。React 使用单个事件侦听器侦听顶层的所有事件。这对性能有好处,也意味着 React 在更新 DOM 时不需要跟踪事件监听器。
# React中的事件是什么?
在 React 中,事件是对鼠标悬停、鼠标单击、按键等特定操作的触发反应。处理这些事件类似于处理 DOM 元素中的事件。但是有一些语法差异,如:
- 用驼峰命名法对事件命名而不是仅使用小写字母。
- 事件作为函数而不是字符串传递。
事件参数重包含一组特定于事件的属性。每个事件类型都包含自己的属性和行为,只能通过其事件处理程序访问。
# 如何在React中创建一个事件?
class Display extends React.Component({
show(evt) {
// code
},
render() {
// Render the div with an onClick prop (value is a function)
return (
<div onClick={this.show}>Click Me!</div>
);
}
});
2
3
4
5
6
7
8
9
10
11
# React中的合成事件是什么?
合成事件是围绕浏览器原生事件充当跨浏览器包装器的对象。它们将不同浏览器的行为合并为一个 API。这样做是为了确保事件在不同浏览器中显示一致的属性。
# 两种绑定事件
<button onClcik={bindClcik1.bind(this)}> 使用 .bind(this) </button>
<button onClcik={bindClcik2}> 箭头函数 </button>
// 使用 class 的自带函数,需要重定向 this
bindClcik1 () { alert('bindClcik1') }
// 使用静态方法,使用箭头函数不需要使用 bind(this)
bindClick2 = () => { alert('bindClcik2') }
2
3
4
5
6
7
# 事件合成机制
- 所有事件挂载到 document 上
- event 不是原生的,是
SyntheticEvent
合成事件对象 - 与 Vue 事件不同,和 DOM 事件也不同
# 为什么要合成事件机制
- 更好的兼容性和跨平台,摆脱传统DOM事件
- 挂载到document,减少内存消耗,避免频繁解绑
- 方便事件的统一管理,如:事务机制
# Event、默认事件、事件冒泡
**这里打印出来的Event
对象是 React 封装过的SyntheticEvent
,可以看__proto__.constructor
。**React 标准化了事件对象,因此在不同的浏览器中都会有相同的属性。
React 中事件绑定跟 Vue 中完全不同,Vue中事件绑定和触发的对象为同一元素,React中事件触发的对象为document
,绑定元素为当前元素。React的所有事件都会被挂载到document
上和DOM
事件不同。
Vue 的Event
是原生,事件被挂载到当前元素和DOM
事件一样;
<a href="www.baidu.com" onClick={this.getEvent} target="blank">Get Event</a>
getEvent = (event) => {
event.preventDefault() // 阻止默认事件
event.stopPropagation() // 阻止事件冒泡
console.log(event) // 非原生的 Event
console.log(event.nativeEvent) // 获取原生的 Event
console.log(event.nativeEvent.target) // 绑定事件的对象,这里为 <a></a>
console.log(event.nativeEvent.currentTarget) // 触发事件的对象,这里为 document
}
2
3
4
5
6
7
8
9
10
# 事件传参
通过.bind()
传参
<div onClick={this.getParams1.bind(this, 'id1', 'title1')}>get params 1</div>
getParams1 (id, title, event) {
console.log('id', id)
console.log('title', title)
console.log('event', event) // 最后一个参数为Event对象
}
2
3
4
5
6
7
通过箭头函数传参
<div onClick={(event) => { this.getParams2('id2', 'title2', event) }}>get params 2</div>
getParams2 (id, title, event) {
console.log('id', id)
console.log('title', title)
console.log('event', event)
}
2
3
4
5
6
7
# 数据持久化
# React 数据持久化有什么实践吗?
封装数据持久化组件:
】let storage={
// 增加
set(key, value){
localStorage.setItem(key, JSON.stringify(value));
},
// 获取
get(key){
return JSON.parse(localStorage.getItem(key));
},
// 删除
remove(key){
localStorage.removeItem(key);
}
};
export default Storage;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
在React项目中,通过redux存储全局数据时,会有一个问题,如果用户刷新了网页,那么通过redux存储的全局数据就会被全部清空,比如登录信息等。这时就会有全局数据持久化存储的需求。首先想到的就是localStorage,localStorage是没有时间限制的数据存储,可以通过它来实现数据的持久化存储。
但是在已经使用redux来管理和存储全局数据的基础上,再去使用localStorage来读写数据,这样不仅是工作量巨大,还容易出错。那么有没有结合redux来达到持久数据存储功能的框架呢?当然,它就是redux-persist。**redux-persist会将redux的store中的数据缓存到浏览器的localStorage中。**其使用步骤如下:
(1)首先要安装redux-persist:
npm i redux-persist
(2)对于reducer和action的处理不变,只需修改store的生成代码,修改如下:
import {createStore} from 'redux'
import reducers from '../reducers/index'
import {persistStore, persistReducer} from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';
const persistConfig = {
key: 'root',
storage: storage,
stateReconciler: autoMergeLevel2 // 查看 'Merge Process' 部分的具体情况
};
const myPersistReducer = persistReducer(persistConfig, reducers)
const store = createStore(myPersistReducer)
export const persistor = persistStore(store)
export default store
2
3
4
5
6
7
8
9
10
11
12
13
14
(3)在index.js中,将PersistGate标签作为网页内容的父标签:
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux'
import store, {persistor} from './redux/store/store'
import {PersistGate} from 'redux-persist/lib/integration/react';
ReactDOM.render(<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
{/*网页内容*/}
</PersistGate>
</Provider>, document.getElementById('root'));
2
3
4
5
6
7
8
9
10
这就完成了通过redux-persist实现React持久化本地数据存储的简单应用。
# 在React中页面重新加载时怎样保留数据?
这个问题就设计到了数据持久化, 主要的实现方式有以下几种:
- Redux: 将页面的数据存储在redux中,在重新加载页面时,获取Redux中的数据;
- data.js: 使用webpack构建的项目,可以建一个文件,data.js,将数据保存data.js中,跳转页面后获取;
- sessionStorge: 在进入选择地址页面之前,componentWillUnMount的时候,将数据存储到sessionStorage中,每次进入页面判断sessionStorage中有没有存储的那个值,有,则读取渲染数据;没有,则说明数据是初始化的状态。返回或进入除了选择地址以外的页面,清掉存储的sessionStorage,保证下次进入是初始化的数据
- history API: History API 的
pushState
函数可以给历史记录关联一个任意的可序列化state
,所以可以在路由push
的时候将当前页面的一些信息存到state
中,下次返回到这个页面的时候就能从state
里面取出离开前的数据重新渲染。react-router 直接可以支持。这个方法适合一些需要临时存储的场景。
# 性能优化
性能优化,永远是面试的重点,性能优化对于 React 更加重要
- 在页面中使用了
setTimout()
、addEventListener()
等,要及时在componentWillUnmount()
中销毁; - 使用异步组件;
- 使用
React-loadable
动态加载组件; shouldComponentUpdate
(简称SCU )、React.PureComponent
、React.memo
;- 不可变值 ImmutableJS;
# shouldComponentUpdate 用途
- 性能优化
- 配合“不可变值”一起使用,否则会出错
# 什么情况下需要使用 shouldComponentUpdate
在React中,默认情况下,如果父组件数据发生了更新,那么所有子组件都会无条件更新 !!!!!! 通过
shouldComponentUpdate()
retrun fasle 来判断阻止 Header 组件做无意义的更新shouldComponentUpdate()
并不是每次都需要使用,而是需要的时候才会优化
shouldComponentUpdate (nextProps, nextState) {
return true // 可以渲染,执行 render(),默认返回 true
return false // 不能渲染,不执行 render()
}
2
3
4
class App extends React.Component {
constructor () {
this.state = { list: [] }
}
render () {
return (
<div>
{/* 当list数据发生变化时,Header组件也会更新,调用 render() */}
<Header />
<List data={this.state.list}
</div>
)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
在shouldComponentUpdate()
判断中,有一个有意思的问题,解释为什么 React setState()
要用不可变值
// 父组件中
changeList () {
this.state.list.push({id: 2})
this.setState({
list: this.state.list
})
}
// 子组件中
import _ from 'lodash'
shouldComponentUpdate(nextProps, nextState) {
// 数组深度比较(一次性递归到底,耗费性能,工作中慎用)
if (_.isEqual(nextProps.list, this.props.list)) {
return false // 相等,不渲染
}
return true // 不相等,渲染
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
子组件将始终不会渲染,因为在
shouldComponentUpdate()
中,this.state.list.push()
已经修改了this.props.list
,而this.setState()
修改了nextProps.list
所以两个值深度比较,将始终相同。
# PureComponent 和 memo
- class类组件中用
PureComponent
,无状态组件(无状态)中用memo
; - PureComponent, SCU中实现了浅比较
- 浅比较已使用大部分情况(尽量不要做深度比较)
PureComponent 与普通 Component 不同的地方在于,PureComponent自带了一个
shouldComponentUpdate()
,并且进行了浅比较
// memo用法
function MyComponent (props) {
/* 使用 props 渲染 */
}
// areEqual 也可不传
function areEqual(prevProps, nextProps) {
if (prevProps.seconds===nextProps.seconds) {
return true
} else {
return false
}
}
export default React.memo(MyComponent, areEqual)
2
3
4
5
6
7
8
9
10
11
12
13
# immutable.js
- 彻底拥抱“不可变值”
- 基础共享数据(不是深拷贝),速度快
- 有一定学习和迁移成本
# 参考链接
https://juejin.cn/post/6844903816857403405
https://juejin.cn/post/6844903892853981198
https://juejin.cn/post/6940287134154637326
https://juejin.cn/post/6940942549305524238
https://juejin.cn/post/6844904093492707336