React 高级内容(3)

render函数必须存在

所有的生命周期函数都可以没有,但render函数必须存在,否则报错。
原因:
组件时继承自react的Component的,其中内置了其他所有的生命周期函数,唯独没有render函数。
所以我们的render函数是必须自己定义的。

React生命周期函数的使用场景

http://localhost:3000/中**设置 highlight,那么页面上被 重新渲染的 组件 就会被框起来:
highlight
可以发现
输入input框时不仅父组件被渲染,子组件也被渲染** :
子组件也被渲染

利用它做性能优化

需优化原因

需优化原因:父组件数据更新 子组件render也被执行

例子:子组件TodoItem

在子组件TodoItem的render函数中加一个打印语句:

1
console.log("child render");

提交11时第一次执行Todoltem的render函数,在父组件的输入框中输入9个数字时不仅执行父组件的render函数9次,还执行了子组件Todoltem的render函数9次
结果

这就是我们之前讲的“当父组件的render函数被运行时,它的子组件的render都将被重新运行一次”。
可实际上并不需要执行子组件的render,所以我们可以使用生命周期函数来做性能优化。

使用shouldComponentUpdate

注意: shouldComponentUpdate 应该需要自带两个参数(nextProps, nextState),他们代表即将更新的两个数据。

在上一篇笔记 React高级内容(2) 的“React的生命周期函数”中我们提到过,不管state还是props更新都要先通过shouldComponentUpdate来判断是否需要更新。

在子组件TodoItem中使用 shouldComponentUpdate 做性能优化,避免content无变化时被父组件更新带动执行子组件render

1
2
3
4
5
6
7
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.content !== this.props.content) {
return true;
} else {
return false;
}
}

可以看到子组件的render仅在提交1时执行了一次,后面父组件的10次改动都并未执行子组件的render:
优化后

总结:我们使用 shouldComponentUpdate 函数减少了虚拟DOM的比对,实现了性能优化。


发生 AJAX请求 列表数据

如果我们希望通过AJAX请求得到输入框下面的 列表数据 ,我们需要明确:
首先,不能把AJAX请求放在render函数中,因为数据更新时render会被再次执行。
所以我们需要放在组件中一个 只会被执行一次的函数 中,那么我们可以在父组件中添加 componentDidMount 函数,在页面渲染好(render函数执行后)触发 AJAX请求。

使用componentDidMount函数

为什么

  1. 我们需要放在组件中一个 只会被执行一次的函数 中。
  2. componentWillMount函数 也只执行一次,但是到了react native或者更深的时候放在componentWillMount函数中就会报错,所以我们默认将AJAX请求 放在 componentDidMount里。
  3. 虽然constructor构造函数也只执行一次,但还是建议将AJAX请求 放在 componentDidMount里。

例子:TodoList中发送AJAX请求

借助axios模块
  • 注意:axios可自动转换 JSON 数据,从后端获取的JSON数据并不需要通过JSON方法去转换为JS对象,直接就可使用JS对象。
  • React并不像jquery那样封装了AJAX发送的内置功能,所以我们需要借助 第三方模块axios。
  • 客户端支持防御 XSRF

安装第三方模块axios
在程序终端输入yarn add axiosnpm与yarn的区别),加载完成后输入npm start重启服务器:
安装第三方模块axios

在父组件TodoList中引用axios

1
import axios from "axios";

通过axios发送AJAX请求获取接口内容

1
2
3
4
5
6
7
8
// render执行后发生AJAX请求
componentDidMount() {
// 通过axios发送AJAX请求获取接口内容
axios.get("/api/todolist")
// 请求成功则执行then,失败执行catch
.then(() => { alert("成功"); })
.catch(() => { alert('失败'); })
}

AJAX是用JavaScript执行异步网络请求Promise实例 里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,Promise实例拥有then()和catch()。)

当然并没有这个接口,所以最后会显示:
请求失败

但是我们点开network可以发现确实发送了一个 http://localhost:3000/api/todolist 的请求,只不过404了:
network


回顾学过的性能优化(面试)

  1. 在constructor函数里绑定 事件函数 的 this指向(bind),而不是在事件函数内绑定,这样可以保证整个程序里只绑定一次,且可以避免 子组件 的无谓渲染。(后面会细讲)
  2. React的底层内置的setState是异步的,可以把多次的数据改变结合成一次来做,降低了 虚拟DOM 的比对频率
  3. React的底层用了虚拟DOM的概念,替代了之前的 真实的DOM(JS对象)
  4. React的 diff算法: 同层比对、key值的运用 来提升 虚拟DOM 比对的速度
  5. 借助生命周期函数shouldComponentUpdate来提高组件性能,避免不必要的子组件render函数的渲染更新

使用Charles实现本地接口数据mock

在上面我们调用接口获取数据时因为接口不存在所以爆出404,但是前端开发和后端是分离的,所以我们就需要在没有接口的情况下 使用 Charles 进行接口数据模拟

Charles 是一个抓包工具,他可以抓住我们的浏览器向外发送的请求并进行处理

工作原理
我们可以在tools里的map local中进行设置,Charles抓取到“map from”中的地址请求就会返回“map to”中选择的文件的数据。
Charles就是一个中间代理服务器。

比如:他看到我们请求的接口地址是“http://http://localhost.charlesproxy.com:3000/api/todolist”,那么通过Charles我们可以设置 当接收这个地址的请求时就返回我们桌面的todolist.json文件内的数据,这也就起到了接口模拟数据的作用。

下载安装 Charles

Charles代理Chrome

Charles在Windows下,默认只代理IE浏览器,对 Chrome 需要设置后才能抓包。
SwitchySharp工具下载地址
Chrome设置教程


Charles抓localhost的包

弄好之后可以发现确实可以抓取chrome的包,但并不能抓取localhost的包,
Charles官方对不能捕获localhost本地网页的说明,以及解决方法。全文大致意思如下:

Localhost流量不会出现在Charles中
某些系统被硬编码为不使用代理进行本地主机流量,因此当您连接到http:// localhost /时,它不会显示在Charles中。
解决方法是连接到http://localhost.charlesproxy.com/。这指向IP地址127.0.0.1,因此它应该与localhost完全相同,但它的优势在于它将通过Charles。无论Charles是在跑还是你在使用Charles,这都会有效。如果您使用其他端口,例如8080,只需像往常一样添加它,例如localhost.charlesproxy.com:8080。
您还可以在该域前放置任何内容,例如myapp.localhost.charlesproxy.com,它也将始终解析为127.0.0.1。
或者,您可以尝试添加.在localhost之后,或用本机名称替换localhost,或使用本地链接IP地址(例如192.168.1.2)。
如果Charles正在运行并且您使用Charles作为代理,那么您也可以使用local.charles作为localhost的替代方案。请注意,这仅在您使用Charles作为代理时才有效,因此上述方法是首选方法,除非您特别希望请求在不使用Charles时失败。

解决方法
原本的网址:http://localhost:3000/
现在换成:http://localhost.charlesproxy.com:3000/
成功抓包(例子可看下方)


例子(TodoList)

需要实现的功能:在TodoList中发送的AJAX请求(接口)获取到桌面上todolist.json文件内的内容并显示在输入框下作为默认的无序列表。

获取到todolist.json内数据

桌面右键新建文件todolist.json并放入数据(这就是接口的模拟数据):

1
["你好呀","你叫什么名字?","我是胡萝卜"]

http://localhost.charlesproxy.com:3000/中我们可以看到**AJAX请求的接口地址**:
接口地址

希望在 TodoList.js 组件中发送AJAX请求时将桌面上的todolist.json文件内的数据返回过来,则需要借助 Charles 进行设置。
打开 Charles,进行设置,抓取到请求的接口地址是“http://localhost.charlesproxy.com:3000/api/todolist”时就返回`todolist.json`文件的数据:
步骤1
步骤2

发送的AJAX请求 数据获取成功
发送的AJAX请求 数据获取成功

可以看到获取到的数据
可以看到获取到的数据


显示在页面上

我们先修改TodoList.js的代码,打印看看成功接收到的数据res

1
2
3
4
5
6
7
8
9
componentDidMount() {
// 通过axios发送AJAX请求获取接口内容
axios.get("/api/todolist")
// 请求成功则执行then,失败执行catch
.then((res) => {
console.log(res);
})
.catch(() => { alert('失败'); })
}

可以注意到res里面有一个 数组data 包含我们想要的数据:
接收到的数据res

修改then函数中的打印语句:

1
console.log(res.data);

可以看到data就是我们想要显示在页面上的数据
data数组

我们知道,在React中,修改state数据页面就会重新渲染,而state中的列表是保存在list: []中的,所以我们可以使用setState改变state中的list数据使data数组成为无序列表显示在输入框下
复习:setState()中最好传入一个函数作为参数,记得return

1
2
3
4
5
6
7
8
9
10
11
12
13
componentDidMount() {
// 通过axios发送AJAX请求获取接口内容
axios.get("/api/todolist")
// 请求成功则执行then,失败执行catch
.then((res) => {
this.setState(() => {
return {
list: res.data
}
});
})
.catch(() => { alert('获取失败'); })
}

结果:todolist.json文件中的内容显示在输入框下面,并且可以实现点击删除,也可以提交新的数据
结果

思路
state中的list更新后, 父组件TodoList 就会重新执行 render函数 ,那么 render函数 中的 getTodoItem函数 也会被重新执行,在getTodoItem函数中会遍历list中的每一个数据并把他们依次传入 子组件TodoItem 中,在 子组件TodoItem 中每一项都会实现 点击删除 的功能,返回的子组件们就会在父组件的ul标签中形成无序列表。


优化代码

函数中返回的代码只有一句时,我们可以使用()包裹代码从而代替函数的{}return:
优化代码1
(相关原因可看笔记”函数相关知识点补充“中的 箭头函数)

我们之前把data放到list中list: res.data,但**最好使用扩展运算符将其复制到list中: list: [...res.data]**,以免因为数据的改动产生不好的影响。


,