内容简介:一个异步请求,当请求返回的时候,拿到数据马上setState并把loading组件换掉,很常规的操作。但是,当那个需要setState的组件被卸载的时候(切换路由、卸载上一个状态组件)去setState就会警告:于是,一个很简单的方法也来了:问题fix。
一个异步请求,当请求返回的时候,拿到数据马上setState并把loading组件换掉,很常规的操作。但是,当那个需要setState的组件被卸载的时候(切换路由、卸载上一个状态组件)去setState就会警告:
于是,一个很简单的方法也来了:
// 挂载 componentDidMount() { this._isMounted = true; } // 卸载 componentWillUnmount() { this._isMounted = false; } // 请求 request(url) .then(res => { if (this._isMounted) { this.setState(...) } }) 复制代码
问题fix。
1. 不想一个个改了
项目肯定不是简简单单的,如果要考虑,所有的异步setState都要改,改到何年何日。最简单的方法,换用preact,它内部已经考虑到这个case,封装了这些方法,随便用。或者console它的组件this,有一个 __reactstandin__isMounted
的属性,这个就是我们想要的 _isMounted
。
不过,项目可能不是说改技术栈就改的,我们只能回到原来的react项目中。不想一个个搞,那我们直接改原生的生命周期和setState吧。
// 我们让setState更加安全,叫他safe吧 function safe(setState, ctx) { console.log(ctx, 666); return (...args) => { if (ctx._isMounted) { setState.bind(ctx)(...args); } } } // 在构造函数里面做一下处理 constructor() { super(); this.setState = a(this.setState, this); } // 挂载 componentDidMount() { this._isMounted = true; } // 卸载 componentWillUnmount() { this._isMounted = false; } 复制代码
2. 不想直接改
直接在构造函数里面改,显得有点耍流氓,而且不够优雅。本着代码优雅的目的,很自然地就想到了装饰器 @
。如果项目的babel不支持的,安装 babel-plugin-transform-decorators-legacy
,加入babel的配置中:
"plugins": [ "transform-decorators-legacy" ] 复制代码
考虑到很多人用了 create-react-app
,这个脚手架原本不支持装饰器,需要我们修改配置。使用命令 npm run eject
可以弹出个性化配置,这个过程不可逆,于是就到了webpack的配置了。如果我们不想弹出个性化配置,也可以找到它的配置文件: node_modules => babel-preset-react-app => create.js
,在plugin数组加上 require.resolve('babel-plugin-transform-decorators-legacy')
再重新启动项目即可。
回到正题,如果想优雅一点,每一个想改的地方不用写太多代码,想改就改,那么可以加上一个装饰器给组件:
function safe(_target_) { const target = _target_.prototype; const { componentDidMount, componentWillUnmount, setState, } = target; target.componentDidMount = () => { componentDidMount.call(target); target._isMounted = true; } target.componentWillUnmount = () => { componentWillUnmount.call(target); target._isMounted = false; } target.setState = (...args) => { if (target._isMounted) { setState.call(target, ...args); } } } @safe export default class Test extends Component { // ... } 复制代码
这样子,就封装了一个这样的组件,对一个被卸载的组件setstate的时候并不会警告和报错。
但是需要注意的是,我们装饰的只是一个类,所以类的实例的this是拿不到的。在上面被改写过的函数有依赖this.state或者props的就导致报错,直接修饰构造函数以外的函数实际上是修饰原型链,而构造函数也不可以被修饰,这些都是没意义的而且让你页面全面崩盘。所以,最完美的还是直接在constructor里面修改this.xx,这样子实例化的对象this就可以拿到,然后给实例加上生命周期。
// 构造函数里面 this.setState = safes(this.setState, this); this.componentDidMount = did(this.componentDidMount, this) this.componentWillUnmount = will(this.componentWillUnmount, this) // 修饰器 function safes(setState, ctx) { return (...args) => { if (ctx._isMounted) { setState.bind(ctx)(...args); } } } function did(didm, ctx) { return(...args) => { ctx._isMounted = true; didm.call(ctx); } } function will(willu, ctx) { return (...args) => { ctx._isMounted = false; willu.call(ctx); } } 复制代码
3. 添加业务生命周期
我们来玩一点更刺激的——给state赋值。
平时,有一些场景,props下来的都是后台数据,可能你在前面一层组件处理过,可能你在constructor里面处理,也可能在render里面处理。比如,传入1至12数字,代表一年级到高三;后台给stringify过的对象但你需要操作对象本身等等。有n种方法处理数据,如果多个人开发,可能就乱了,毕竟大家风格不一样。是不是想过有一个beforeRender方法,在render之前处理一波数据,render后再把它改回去。
// 首先函数在构造函数里面改一波 this.render = render(this.render, this); // 然后修饰器,我们希望beforeRender在render前面发生 function render(_render, ctx) { return function() { ctx.beforeRender && ctx.beforeRender.call(ctx); const r = _render.call(ctx); return r; } } // 接着就是用的问题 constructor() { super() this.state = { a: 1 } this.render = render(this.render, this); } beforeRender() { this._state_ = { ...this.state }; this.state.a += 100; } render() { return ( <div> {this.state.a} </div> ) } 复制代码
我们可以看见输出的是101。改过人家的东西,那就得改回去,不然就是101了,你肯定不希望这样子。didmount或者didupdate是可以搞定,但是需要你自己写。我们可以再封装一波,在背后悄悄进行:
// 加上render之后的操作: function render(_render, ctx) { return function(...args) { ctx.beforeRender && ctx.beforeRender.call(ctx); const r = _render.call(ctx); // 这里只是一层对象浅遍历赋值,实际上需要考虑深度遍历 Object.keys(ctx._state_).forEach(k => { ctx.state[k] = ctx._state_[k]; }) return r; } } 复制代码
一个很重要的问题,千万不要 this.state = this._state_
,比如你前面的didmount在几秒后打印this.state,它还是原来的state。因为那时候持有对原state对象的引用,后来你赋值只是改变以后state的引用,对于前面的dimount是没意义的。
// 补上componentDidMount可以测试一波 componentDidMount() { setTimeout(() => { this.setState({ a: 2 }) }, 500); setTimeout(() => { console.log(this.state.a, '5秒结果') // 要是前面的还原是this.state = this._state_,这里还是101 }, 5000); } 复制代码
当然,这些都是突发奇想的。考虑性能与深度遍历以及扩展性,还是有挺多优化的地方,什么时候要深度遍历,什么时候要赋值,什么时候可以换一种姿势遍历或者什么时候完全不用遍历,这些都是设计需要思考的点。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
重新定义团队:谷歌如何工作
拉兹洛·博克 / 宋伟 / 中信出版集团 / 2015-12-1 / CNY 56.00
谷歌首席人才官拉斯洛•博克权威力作,谷歌公开认可的谷歌高层作品,首度揭秘谷歌颠覆工业时代模式的人才和团队管理的核心法则,《纽约时报》畅销榜第一名,Business Insider 2015最佳商业书籍,谷歌的创造力就在于此! 编辑推荐! 1、 谷歌人才官首次公开谷歌人才和团队管理的核心秘籍 在谷歌执掌人事多年的拉斯洛•博克是人才和团队管理的顶级专家。他加入谷歌后,谷歌的员工数从六......一起来看看 《重新定义团队:谷歌如何工作》 这本书的介绍吧!