# react基础
# 1、综述
# 1、简史及版本
- 2011年诞生
- 2015年发布v0.14.0版本:拆分react package into two,react and react-dom
- 2016年发布v15.0版本:虚拟dom的diff操作同步执行
- 2017年发布React v16.0版本:使用fiber架构(分片),<ie11需使用polyfill
- react v16.2.0(2017年):增加Fragment组件
- react V16.3.0(2018年):增加主要的api,React.createRef()、React.forwardRef()
- react V16.6.0(2018年):增加主要的api,React.memo()、React.lazy()
- react V16.8.0(2019年):增加React Hooks,用来解决状态逻辑复用问题,且不会产生 JSX 嵌套地狱
- 2020年发布React v17.0版本:引入渐进式升级
# 2、生态
脚手架/框架
- umi:可插拔的企业级react应用框架
- create react app:官方支持的创建react单页应用程序的方法
- Nextjs:ssr框架
- React-vr:vr框架,适用于展厅、房屋设计
- Reactxp:多端框架
组件库
- ant Design系列:pc、mobile,引入Ant Design设计概念
- Material-UI:实现了谷歌Material Design设计规范,世界流行界面
工具类
- Redux:遵循函数式编程思想的状态管理插件
- Mobx:面向对象变迁和响应式编程的状态管理插件
- Immutable-js:解决javasript Immutable Data的问题
跨端类
- Remax:阿里的React跨端框架,目前支持支付宝、微信、字节小程序
- Taro:类React跨端框架,支持主流小程序及React Native
- React Native:js编写原生的React框架
其他
- react-window和react-virtualized:虚拟滚动库,提供可服用组件,用于展示列表、网络和表格数据。
# 2、基础
# 1、JSX
- 合法的jsx元素
- 普通的dom标签,如div/p/span等
- 申明的react组件,例如通过class或者函数创建的jsx组件,用户定义的组件必须以大写字母开头
- false、null、undefined、true为合法元素,不会渲染
- 字符串(最终会渲染一个text节点)
- 数字类型,最终会渲染出来
{false && (<p>test</p>)} {/* 渲染空元素 */}
{0 && (<p>true</P>)} {/* 与门如果出现非布尔值,渲染与预期有出入,渲染出0 */}
{0 ? null : (<p>true</P>)} {/* 建议使用三目运算符 */}
2
3
- 语法
- {}使用js表达式:三目运算符、执行函数、数据map等
- class/for这类html属性是关键字,使用className、htmlFor的形式来定义
- props默认值为true
- 点语法,一个模块导出多个组件,可以使用点语法直接使用
- 注释:{/* xxx */}
//点语法
import React from 'react';
const MyComponents = {
DatePicker: function DatePicker(props) {
return <div>Imagine a {props.color} datepicker here.</div>;
}
}
function BlueDatePicker() {
const props = {firstName: 'Ben', lastName: 'Hector'};
return <MyComponents.DatePicker color="blue" className="text" {...props}/>;
}
//动态组件
import React from 'react';
import { PhotoStory, VideoStory } from './stories';
const components = {
photo: PhotoStory,
video: VideoStory
};
function Story(props) {
const SpecificStory = components[props.storyType];
return (
<SpecificStory story={props.story}
onClick={() => console.log("clicked!")}/>
);
}
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
- 语法糖
- 本质为React.createElement(component, props, ...children)函数的语法糖
- babel-plugin-transform-react-jsx插件,可以将 jsx 编译为 react 的内部⽅法createElement
# 2、函数组件和class组件
在react中,可以使用class形式或是函数的形式来创建一个组件:
//函数组件
function Foo(props) {
return (
<div>{ props.text || 'Foo'}</div>
);
}
//类组件
class Bar extends React.Component {
render() {
return (
<div>{this.props.text || 'Bar'}</div>
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
两种组件的区别:
加载的 props ⽅式不同,函数式定义组件从组件函数的参数加载;class 形式的组件通过 this.props 获取传⼊的参数
函数式组件⽐较简单,内部⽆法维护状态;class 形式内部可以通过 this.state 和 this.setState ⽅法定义和更新内部state ,同时更新 render ⾥⾯的函数渲染的结果
class 组件内部可以定义更多的⽅法在实例上,但是函数式组件⽆法定义
class组件需要使用new实例化,函数组件直接使用
# 3、有/无状态组件
# 1、有状态组件
特点
- 是类组件
- 有继承
- 可以使用this
- 可以使用react的生命周期
- 使用较多,容易频繁触发生命周期钩子函数,影响性能
- 内部使用 state,维护自身状态的变化,有状态组件根据外部组件传入的 props 和自身的 state进行渲染
使用场景
- 需要使用到状态的
- 需要使用状态操作组件的(无状态组件的也可以实现,通过新版本react hooks也可实现)
总结
类组件可以维护自身的状态变量,即组件的 state ,类组件还有不同的生命周期方法,可以让开发者能够在组件的不同阶段(挂载、更新、卸载)对组件做更多的控制。类组件则既可以充当无状态组件,也可以充当有状态组件。当一个类组件不需要管理自身状态时,也可称为无状态组件
# 2、无状态组件
特点
- 不依赖自身的状态state
- 可以是类组件或者函数组件。
- 可以完全避免使用 this 关键字(由于使用的是箭头函数事件无需绑定)
- 有更高的性能,当不需要使用生命周期钩子时,应该首先使用无状态函数组件
- 组件内部不维护 state ,只根据外部组件传入的 props 进行渲染的组件,当 props 改变时,组件重新渲染。
使用场景
- 组件不需要管理 state,纯展示
优点
- 简化代码、专注于 render
- 组件不需要被实例化,无生命周期,提升性能。 输出(渲染)只取决于输入(属性),无副作用
- 视图和数据的解耦分离
缺点
- 无法使用 ref
- 无生命周期方法
- 无法控制组件的重渲染,因为无法使用shouldComponentUpdate 方法,当组件接受到新的属性时则会重渲染
总结
组件内部状态且与外部无关的组件,可以考虑用状态组件,这样状态树就不会过于复杂,易于理解和管理。当一个组件不需要管理自身状态时,也就是无状态组件,应该优先设计为函数组件,比如自定义的 Button、Input等组件
# 4、受控组件和非受控组件
受控组件:表单数据是由 React 组件来管理的,推荐使用
非受控组件:表单数据将交由 DOM 节点来处理,可以通过ref获取表单中数据
//受控组件
class Form extends Component {
constructor() {
super();
this.state = {
name: '',
};
}
handleNameChange = (event) => {
this.setState({ name: event.target.value });
};
render() {
return (
<div>
<input
type="text"
value={this.state.name}
onChange={this.handleNameChange}
/>
</div>
);
}
}
//非受控组件
class Form extends Component {
handleSubmitClick = () => {
const name = this._name.value;
// do something with `name`
}
render() {
return (
<div>
{/* defaultValue为初始值,通过ref可获取input值 */}
<input type="text" ref={input => this._name = input} defaultValue="Jian"/>
<button onClick={this.handleSubmitClick}>Sign up</button>
</div>
);
}
}
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
# 5、组件生命周期
class组件中,可申明如下生命周期:
# 1、老版本
- componentWillMount:在新节点插入DOM结构之前触发
- render:componentWillMount和componentDidMount之间触发
- componentDidMount:在新节点插入DOM结构之后触发
- 初始化请求最好在componentDidMount中发送请求,之前是服务器渲染阶段,不适合发送请求;请求中可能有操作dom操作,会出现问题。客户端发送请求api最好放在服务器不会渲染的方法里,在组件挂载至dom元素后再进行请求更新
- componentWillUnMount:在组件从DOM中移除时立即触发
- componentWillReceiveProps:已加载的组件收到新的参数时调用,只有props参数更新时才会触发
- shouldComponentUpdate:组件判断是否重新渲染时调用,返回false取消更新组件,不会调用render
- componentWillUpdate:当你的组件再次渲染时,在render方法前调用(在组件的props或者state发生改变时会触发该方法)
- componentDidUpdate:在render函数执行完毕,且更新的组件已被同步到DOM后立即调用,该方法不会在初始化渲染时触发
加载顺序:
首次加载时:componentWillMount -> render -> componentDidMount
参数变化时:componentWillReceiveProps(props参数变化) -> shouldComponentUpdate
-> componentWillUpdate -> render -> componentDidUpdate
组件卸载:componentWillUnMount
# 2、新版本
新增:
- static getDerivedStateFromProps(props, state):在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。条件: state 的值在任何时候都取决于 props
- getSnapshotBeforeUpdate():在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给 componentDidUpdate()
- static getDerivedStateFromError(error):会在后代组件抛出错误后被调用 ,它将抛出的错误作为参数,并返回一个值以更新 state
- componentDidCatch(error, info):在后代组件抛出错误后被调用,用于记录错误之类的情况:
加载顺序:
挂载时:constructor() -> static getDerivedStateFromProps() -> render() -> componentDidMount()
更新时:static getDerivedStateFromProps() -> shouldComponentUpdate()
-> render() -> getSnapshotBeforeUpdate() -> componentDidUpdate()
卸载时:componentWillUnmount()
错误处理:当渲染过程,生命周期,或子组件的构造函数中抛出错误时 static getDerivedStateFromError() -> componentDidCatch()
# 6、事件代理
react中的事件使用事件代理,采用全局单例的event对象,用于跨端、兼容性和性能提升,异步操作最好将对象内部需要的值进行拷贝,否则会导致this指向问题。
import React, {Component} from 'react';
class App extends Component {
clickHandle1(e){
setTimeout(function(){
console.log('button1 click', e.currentTarget.innerText);
}, 1000)
}
clickHandle2(e){
console.log('button2 click', e.currentTarget.innerText);
}
render(){
return (
<div className="App">
{/* click1报错,click2输出button2 */}
{/* 解决方法:
clickHandle1(e){
let text = e.currentTarget.innerText;
setTimeout(function(){
console.log('button1 click', text);
}, 1000)
}
*/}
<button onClick={this.clickHandle1}>button1</button>
<button onClick={this.clickHandle2}>button2</button>
</div>
)
}
}
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
# 7、immutable和immer
- 函数引用:render函数中,使用箭头函数或者bind向子组件传递函数时,每次会重新创建函数,导致子组件进行渲染,影响性能,建议写入constructor里或外部提取使用箭头函数
class App extends React.Component>{
constructor(props){
super(props);
this.state = {
num: 1,
title: "Jian"
}
//推荐写法1,在constructor中使用bind
this.handleClick2 = this.handleClick1.bind(this);
}
handleClick1(){
this.setState({
num: this.state.num + 1
})
}
//推荐写法2,使用箭头函数绑定
handleClick3 = () => {
this.setState({
num: this.state.num + 1
})
}
render(){
return (
<div>
{/* 不推荐写法 */}
<ChildComponent onClick={this.handleClick1.bind(this)}></ChildComponent>
<ChildComponent onClick={()=> this.handleClick1()}></ChildComponent>
{/* 考点1解决方法:提取函数 */}
<ChildComponent onClick={this.handleClick2}></ChildComponent>
<ChildComponent onClick={this.handleClick3}></ChildComponent>
</div>
)
}
}
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
- 对象引用:传递给子组件对象时,使用深拷贝会引起的对象的引用断裂,等价于每次传入子组件中的对象为新对象,导致子组件重新渲染,可使用immutable库或者immer类库避免,可配合 shouldComponentUpdate 进行追踪,来进⾏性能优化
//immutable库使用
const immutable = require('immutable')
const data = {
key1: 'test1',
key2: 'test2'
}
const a = immutable.fromJS(data);//转换为内置对象,
const b = a.set('key1', 'valueb');
console.log(a.key1 === b.key1); //false
console.log(a.key2 === b.key2); //true
//immer库使用
const produce = require('immer')
const state = {
key1: 'test1',
key2: 'test2'
}
const newState = produce(state, (draft) => {
draft.key1 = 'newKey1';
})
console.log(newState.key1 === state.key1); //false
console.log(newState.key2 === state.key2); //true
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 8、常用API
# 1、React.creatElement
React.createElement( //创建并返回指定类型的新React元素
type, //标签名字符串,class组件或函数组件,fragment类型
[props],
[...children]
)
2
3
4
5
# 2、React.Component和React.PureComponent
React.Component是使用 ES6 classes方式定义 React 组件的基类,必须有render函数。
React.PureComponent 与 React.Component很相似,两者的区别在于React.Component并未实现 shouldComponentUpdate()(,而 React.PureComponent`中以浅层对比 prop 和 state 的方式来实现了该函数。
解析:
PureComponent表示一个纯组件,可以用来优化React程序,减少render函数执行的次数,从而提高组件的性能。
在React中,当prop或者state发生变化时,可以通过在shouldComponentUpdate生命周期函数中执行return false来阻止页面的更新,从而减少不必要的render执行。React.PureComponent会自动执行 shouldComponentUpdate。
不过,pureComponent中的 shouldComponentUpdate() 进行的是浅比较,也就是说如果是引用数据类型的数据,只会比较不是同一个地址,而不会比较这个地址里面的数据是否一致。浅比较会忽略属性和或状态突变情况,其实也就是数据引用指针没有变化,而数据发生改变的时候render是不会执行的。如果需要重新渲染那么就需要重新开辟空间引用数据。PureComponent一般会用在一些纯展示组件上。
使用pureComponent的好处:当组件更新时,如果组件的props或者state都没有改变,render函数就不会触发。省去虚拟DOM的生成和对比过程,达到提升性能的目的。这是因为react自动做了一层浅比较。
# 3、context
Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props
在react-router-dom以及redux中常使用,重点掌握内容
React.createContext(defaultValue)
创建一个context
当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的
Provider
中读取到当前的 context 值只有当组件所处的树中没有匹配到Provider时,defaultValue才生效
Context.Provider
- 每个context对象都会返回一个Provider react组件,用于传递数据
- 每个Provider接收value属性,传递给消费组件
- 可一对多,一个Provider对应多个消费者;可嵌套,里层覆盖外层数据
- value值变化,消费组件重新渲染,新旧变化使用Object.is
- provider及其内部consumer组件不受制于shouldComponentUpdate函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新
Context.Consumer
- react组件可以订阅到context变更,用于在函数组件中订阅context
- 子元素为函数,接收context值,返回React节点,{context => (/* 基于context值渲染 */)}
Context.displayName:context对象有一个displayName的属性,类型为字符串,用于devtools显示context的name
Class.contextType:挂载在 class 上的
contextType
属性会被重赋值为一个由React.createContext()
创建的 Context 对象。这能让你使用this.context
来消费最近 Context 上的那个值。你可以在任何生命周期中访问到它,包括 render 函数中
// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
// 为当前的 theme 创建一个 context(“light”为默认值)。
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
// 无论多深,任何组件都能读取这个值。
// 在这个例子中,我们将 “dark” 作为当前的值传递下去。
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// 指定 contextType 读取当前的 theme context。
// React 会往上找到最近的 theme Provider,然后使用它的值。
// 在这个例子中,当前的 theme 值为 “dark”。
// 使用 static 这个类属性来初始化你的 contextType,Class.contextType的api
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
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
# 4、Ref与Ref转发
- React.createRef():创建ref,通过ref获取dom元素
- React.forwardRef((props, ref)=>{/* */}):转发ref到子组件
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// 你可以直接获取 DOM button 的 ref:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
2
3
4
5
6
7
8
9
# 5、memo
React.memo为高阶组件,props检查,渲染缓存,浅层比较:如果你的组件在相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo
中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。
注意:React.memo
仅检查 props 变更。如果函数组件被 React.memo
包裹,且其实现中拥有 useState
,useReducer
或 useContext
的 Hook,当 state 或 context 发生变化时,它仍会重新渲染
//1、使用第一个参数浅层比较
const MyComponent = React.memo(function MyComponent(props) {
/* 使用 props 渲染 */
});
//2、使用第2个参数可进行深入比较
function MyComponent(props) {
/* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
/*
如果把 nextProps 传入 render 方法的返回结果与
将 prevProps 传入 render 方法的返回结果一致则返回 true,
否则返回 false
*/
}
export default React.memo(MyComponent, areEqual);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17