React随笔

React 中的this

React 的官方文档里写的自定义的方法都需要用 bind 方法绑定一下才能使用,否则 this 会出现指向问题,那究竟是为什么呢?

探讨一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person {
constructor(props){
this.state = {
......
}
}
render(){
return <div onClick={demo}>你好,DexterHwang</div>
}
}

function demo(){
console.log(this.state) // 会报错,注意此处的this
}

ReactDom.render(<Person />,document.getElementById('app'))

React 中的 Babel 使用了严格模式,所以写的全局的 this指向的是undefined

而且 demo 方法并不写在 Person 类中,所以这个 this 也不会指向 Person 的实例对象

探讨二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Person {
constructor(props){
this.state = {
......
}
}
render(){
return <div onClick={demo}>你好,DexterHwang</div> // 会报错,报错信息为:`demo is not defined`。`onClick={demo}`是找不到 `demo` 方法的
// return <div onClick={this.demo}>你好,DexterHwang</div> // 这样写也会报错,因为并不是通过实例对象去调用demo方法。由于demo是作为onClick的回调,不是通过实例调用的,是直接调用。类中的方法默认开启了局部的严格模式,所以this值是undefined
}
// demo方法在Person的原型对象(prototype)上,也就是在Person的实例对象的原型链上(_proto_)
// 只有通过Person实例对象调用demo方法时,demo里面的this指向的就是Person的实例对象
demo(){
console.log(this)
}
}

ReactDom.render(<Person />,document.getElementById('app'))

我们看下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
study() {
console.log(this);
}
}
const p1 = new Person("DexterHwang", 18);

p1.study(); //{name:'DexterHwang',age:18}
const x = p1.study;
x(); // 输出undefined

p1 的原型链上是可以找到 study 方法的,然后把 study 方法赋值给了 x。也就相当于在栈上多了一个指针指向了 study 方法,这下 x 就彻底和 p1 没有关系了

另外,再明确一点:_类中自定义的方法,都会启用局部严格模式_。也就是说 this 的值是 undefined

也就是说 x 执行后 this 值就是 undefined

验证:类中自定义的方法,都会启用局部严格模式

1
2
3
4
5
6
7
8
9
10
11
function test() {
console.log(this);
}

function test2() {
"use strict";
console.log(this);
}

test(); // window
test2(); // undefined

分析 this.demo = this.demo.bind(this)

为什么用 bind()就可以解决 this 指向问题呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 class Person {
constructor(props){
this.state = {
......
}
this.demo = this.demo.bind(this)
}
render(){
return <div onClick={this.demo}>你好,DexterHwang</div>
}

demo(){
console.log(this)
}
}

ReactDom.render(<Person />,document.getElementById('app'))

// 点击后会输出:Person{...}

分析this.demo = this.demo.bind(this)

等号右侧的第一个 this 指向的是实例对象,这个实例对象上有 demo 方法吗?有,但是实在原型链上的。

bind 方法做两件事:

  1. 将 this 牢牢的绑定到传入的参数上
  2. 返回一个新的函数

等号右边第二个 this,也就是传入的参数,这个 this 指的就是实例对象

这样的话返回了一个新的函数,而且这个函数牢牢的绑定到了实例对象上,并不需要去原型链上找了

再看等号左侧,将返回的新函数赋值给了 this,这个 this 也是 Person 实例对象,用相同的方法名接收一下

这样就可以使用了

我们这样再去看onClick={this.demo},这下再点击后执行的就是实例自身上的 demo 方法了。


2021-12-25 更新—— React17 对比新旧生命周期

新的生命周期和旧的生命周期相比,即将废弃三个钩子:componentWillMountcomponentWillUpdatecomponentWillReceiveProps

新增了两个新的钩子:getDerivedStateFromPropsgetSnapshotBeforeUpdate

剩下的都和之前的一样的

2021-12-16 更新—— React 配置代理

单个代理可以直接在package.json文件中配置proxy

多个代理则需要新建一个setupProxy.js文件,配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const proxy = require("http-proxy-middleware");

module.exports = function (app) {
app.use(
proxy("/api1", {
//遇见api1前缀的请求,就会触发这个代理配置
target: "http://localhost:5000", // 请求转发给谁,也就是服务器地址
changeOrigin: true, // 控制服务器收到的请求头中Host的值,这里是localhost:5000
pathRewrite: { "^/api1": "" }, // 重写请求路径
}),
proxy("/api2", {
target: "http://localhost:5001",
changeOrigin: true,
pathRewrite: { "^/api2": "" },
})
);
};

create-react-app脚手架会自动找到setupProxy.js文件,并将配置加入到 webpack 中

2021-12-27 更新—— React 路由原理

靠的是 H5 推出的 history 上的 API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//路由跳转
function push(path) {
history.push(path);
return false;
}

// 路由替换
function replace(path) {
history.replace(path);
}

// 路由回退
function back() {
history.goBack();
}

// 路由前进
function forword() {
history.goForward();
}

// 监听路由变化
history.listen((location) => {
console.log("监听路由变化");
});

2021-12-28 更新—— 全局事件总线

用第三方库mitt或者pubsub,可以实现多层级的组件之间的通信

2021-12-30 更新—— React 的 setState()

异步执行

  1. 多个 setState()会推到一个任务队列里面,将多次执行合并为一个来执行。

  2. setState()的几种写法

1
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
//常规写法,这种写法会将多次setState()方法合并
// counter的初始值为1
this.setState({
counter: this.state.counter + 1,
});
this.setState({
counter: this.state.counter + 1,
});
this.setState({
counter: this.state.counter + 1,
});
// counter的值是2
// ----------------------------------------
// 这种写法和上面执行结果不同,preState拿到的是最新的设置后的值(上一个setState设置的值)
this.setState((preState) => ({
counter: preState.counter + 1,
}));

this.setState((preState) => ({
counter: preState.counter + 1,
}));

this.setState((preState) => ({
counter: preState.counter + 1,
}));
// counter的值是4

setState()方法里的第二个参数是一个回调函数,该函数能拿到设置之后的值

2021-12-31 更新—— 无副作用

在 React 里经常看到无副作用这个词,它表示它只对自身有影响对自身以外的无影响,所有的输出都是由我的输入来决定的。

发一个请求,设置了 localstorage,对外部进行了操作这都叫做副作用

React 生命周期

  1. 组件初始化阶段 initialization, 比如 constructor
  2. 组件挂载阶段 mount
    1. componentWillMount 组件挂载到 DOM 前调用,只会被调用一次, 这里写 setState 不会引起组件重新渲染
    2. render 返回一个 react 元素, react 根据此函数的返回值渲染 DOM. 不能在这里 setState
    3. componentDidMount 组件挂载到 DOM 后调用, 且只会被调用一次
  3. 组件的更新阶段 update
    1. componentWillReceiveProps(nextProps) 触发于 props 引起的组件更新过程中
    2. shouldComponentUpdate(nextProps, nextState) 比较之前和当前的 props state 是否有变化
    3. componentWillUpdate(nextProps, nextState) render 方法前执行
    4. render
    5. componentDidUpdate(preProps, preState)
  4. 组件的卸载阶段 unmount
    1. componentWillUnmount 卸载前调用, 在这里可以清理一些定时器

componentWillMount,componentWillReceiveProps,componentWillUnmount在 React17 不建议使用,即将被废弃

2022-06-30 更新 React 的虚拟 DOM

在 React,我们操作的元素被称为 React 元素,并不是真正的原生 DOM 元素。

React 通过虚拟 DOM,将 React 元素和原生 DOM,进行映射,虽然操作的是 React 元素,但是这些操作最终都会在真实的 DOM 中体现。

虚拟 DOM 的好处:

  • 降低 api 复杂度(远离原生的复杂的 dom 操作)
  • 解决兼容性问题
  • 提升性能(减少 DOM 的不必要操作)

每当我们调用 root.render()时,页面就会发生重新渲染

React 会通过 diffing 算法,将新的元素和旧的元素进行比较

通过比较找到发生变化的元素,并且只对变化的元素进行修改,没有发生的变化不予处理。

比较两次数据时,React 会先比较父元素,父元素如果不同,直接所有元素全部替换。父元素一致,再去逐个比较子元素,直到找到所有发生变化的元素为止。

在 JSX 中显示数组时,数组中的每一个元素都需要设置一个唯一的 key,否则控制台会报警告。

重新渲染页面时,React 会按照顺序依次比较对应的元素,当渲染一个列表时如果不指定 key,同样也会按照顺序进行比较。如果列表的顺序永远不会发生变化,不加 key 没有问题。但是如果列表的顺序会发生变化,这可能会导致性能问题。

  • 尽量用元素的 id 来做 key
  • 用遍历的 索引 index 来做 key,没有意义,仍然会全部更新
  • 当元素的顺序不会发生变化时,用索引 index 做 key,没有问题

React随笔
https://zouhualu.github.io/20211218/React随笔/
作者
花鹿
发布于
2021年12月18日
许可协议