React组件中的全局变量

2020年04月16日Web前端0

在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 = []
    // ... ...
  }
}