React组件中的全局变量
在React中,我们推荐一些与UI无关的数据不要存到state上,全局的变量就是个不错的地方。当然这么处理的话,是否会引起一些不必要的问题呢?
文件中的全局变量
假如在项目中,有A页面,A.js如下:
import React from 'react'
let num = 0
const temp = []
export default class A extends React.PureComponent {
componentDidMount() {
temp.push(num++)
console.log(num, temp) // ???
}
render() {
return (
<div>A</div>
)
}
}
此时,A页面与其他页面来回切换时,num和temp分别输出什么呢?他们会保留前面的值么?最后结果是这样的:
实际这两个变量是一直未被释放的,所以每次didmount后操作的都是最先初始化的数据。
不销毁的原因
大家可能很好奇,为啥在页面切离后,这些变量没有被销毁,毕竟他们没有挂载在window上,且使用他们的地方(A组件)已经被销毁了。
那我们从代码层面上看看这是为什么吧,执行run build,在dist下找到文件,将压缩的代码格式化下,如下:
(window.webpackJsonp = window.webpackJsonp || []).push([
[0], {
114: function (t, e, n) {}, 115: function (t, e, n) {
"use strict";
n.r(e);
var o, r = n(46),
u = n.n(r),
c = (n(226), u()());
o = n(227), c.router(o.default || o), c.use({
onHmr: function (t) {}
}), c.start("#root")
}, 164: function (t, e) {}, 226: function (t, e, n) {}, 227: function (t, e, n) {
"use strict";
n.r(e);
var o = n(1),
r = n.n(o),
u = n(25),
c = n(46),
i = n(36),
a = n(114),
f = n.n(a);
function l() {
return r.a.createElement("div", {
className: f.a.normal
}, r.a.createElement(i.a, {
to: "/a"
}, "A"), r.a.createElement(i.a, {
to: "/b"
}, "B"))
}
l.propTypes = {};
var p = Object(c.connect)()(l);
function y(t) {
return (y = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (t) {
return typeof t
} : function (t) {
return t && "function" == typeof Symbol && t.constructor === Symbol && t !== Symbol.prototype ? "symbol" : typeof t
})(t)
}
function s(t, e) {
if (!(t instanceof e)) throw new TypeError("Cannot call a class as a function")
}
function b(t, e) {
for (var n = 0; n < e.length; n++) {
var o = e[n];
o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(t, o.key, o)
}
}
function m(t, e) {
return !e || "object" !== y(e) && "function" != typeof e ? function (t) {
if (void 0 === t) throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
return t
}(t) : e
}
function h(t) {
return (h = Object.setPrototypeOf ? Object.getPrototypeOf : function (t) {
return t.__proto__ || Object.getPrototypeOf(t)
})(t)
}
function v(t, e) {
return (v = Object.setPrototypeOf || function (t, e) {
return t.__proto__ = e, t
})(t, e)
}
var w = 0,
O = [],
d = function (t) {
function e() {
return s(this, e), m(this, h(e).apply(this, arguments))
}
var n, o, u;
return function (t, e) {
if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function");
t.prototype = Object.create(e && e.prototype, {
constructor: {
value: t,
writable: !0,
configurable: !0
}
}), e && v(t, e)
}(e, t), n = e, (o = [{
key: "componentDidMount",
value: function () {
O.push(w++)
}
}, {
key: "render",
value: function () {
return r.a.createElement("div", null, "A")
}
}]) && b(n.prototype, o), u && b(n, u), e
}(r.a.PureComponent);
function E(t) {
return (E = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (t) {
return typeof t
} : function (t) {
return t && "function" == typeof Symbol && t.constructor === Symbol && t !== Symbol.prototype ? "symbol" : typeof t
})(t)
}
function _(t, e) {
if (!(t instanceof e)) throw new TypeError("Cannot call a class as a function")
}
function j(t, e) {
for (var n = 0; n < e.length; n++) {
var o = e[n];
o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(t, o.key, o)
}
}
function S(t, e) {
return !e || "object" !== E(e) && "function" != typeof e ? function (t) {
if (void 0 === t) throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
return t
}(t) : e
}
function P(t) {
return (P = Object.setPrototypeOf ? Object.getPrototypeOf : function (t) {
return t.__proto__ || Object.getPrototypeOf(t)
})(t)
}
function g(t, e) {
return (g = Object.setPrototypeOf || function (t, e) {
return t.__proto__ = e, t
})(t, e)
}
var k = function (t) {
function e() {
return _(this, e), S(this, P(e).apply(this, arguments))
}
var n, o, u;
return function (t, e) {
if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function");
t.prototype = Object.create(e && e.prototype, {
constructor: {
value: t,
writable: !0,
configurable: !0
}
}), e && g(t, e)
}(e, t), n = e, (o = [{
key: "render",
value: function () {
return r.a.createElement("div", null, "B")
}
}]) && j(n.prototype, o), u && j(n, u), e
}(r.a.PureComponent);
e.default = function (t) {
var e = t.history;
return r.a.createElement(u.Router, {
history: e
}, r.a.createElement(u.Switch, null, r.a.createElement(u.Route, {
path: "/",
exact: !0,
component: p
}), r.a.createElement(u.Route, {
path: "/a",
exact: !0,
component: d
}), r.a.createElement(u.Route, {
path: "/b",
exact: !0,
component: k
})))
}
}
},
[
[115, 1, 2]
]
]);
转化后的js有点多,直接搜索didmount函数,找到num和temp变量,当然为了减小js文件体积,两变量名已被压缩了。
此时的calss组件已经被转化成function了,被命名为d,在d上方定义了w和O两个变量,这就是num和temp。再往外层看,他们都处于227这个函数中,也就是说原先定义与A.js文件中的全局变量已经被转化到函数内部的局部变量了。
再搜索下d函数是哪边被用到的,在文件最底部,d被以component传入到React的createElement方法中。
到这的话,应该差不多了。
原因就是class组件被挂到React大实例中,而class组件中有使用两变量,导致两变量以闭包的方式被访问,所以一直未被释放。
解决方法
为了避免老数据对页面造成的影响,我们可以这么修改,
与组件共存
我们可以将这几个全局变量直接挂到组件上,这样变量与组件的生命周期就同步了。如:
export default class A extends React.PureComponent {
num = 0
temp = []
}
使用前初始化
在组件mount或第一次使用数据前,对原有数据进行初始化,如,
export default class A extends React.PureComponent {
componentDidMount() {
num = 0
temp = []
// ... ...
}
}