React基础13 遇到的坑
总结整理了一下一年前使用React开发测评平台时的经验。
总共使用React开发了一个项目,用了1个多月,学到的东西比这一年都多,值得好好总结。
定时任务中的setState
如果定时任务触发时,组件已经被销毁,会给出警告
1 | setState(...): Can only update a mounted or mounting component. |
虽然只是一个warning,但是还是证明写的代码不规范,不一定什么时候就埋坑了。
这个问题实质是:setState
在异步的callback
里执行,而这个时候由于返回上一页,组件已经被销毁了。
用isMounted
方法做判断是官方不推荐的方法,而且我也不知道怎么实现。
真正的解决方法应该是在componentWillUnmount
中将事件清除或者变量设置为null
。
对于setInterval
来说需要在componentWillUnmount
中clear
bind
在addEventListener
中的使用
在组件中添加了scroll
事件:
1 | window.addEventListener('scroll', this.windowScroll.bind(this)); |
在componentWillUnmount
中想要清除绑定的事件:
1 | window.removeEventListener('scroll', this.windowScroll.bind(this)); |
这样做是不会生效的。
因为bind(this)
方法总会返回一个新的函数,所以在removeEventListener
时,移除的是一个不存在的、新的函数。
解决方法是在constructor
里面对windowScroll
一次性绑定this
。(这样this.windowScroll
变量指向的就是同一个bind
之后的方法了。
1 | export default class Overview extends React.Component { |
globalStore
中的方法在组件中不能直接调用
1 | const { globalStore } = this.props; |
这是因为this
指向调用者,前者的this
指向globalStore
,后者指向window
checkbox
值不能正确重置
在不同题目之间跳转时checkbox
值不会清除,原因是在跳转时即使input
所在的组件被重新render
,但是如果input
本身的key
没有变化,React就认为这个组件整体没有变化,不会重新渲染,只会对input
局部渲染,所以input
的value
的值就不会重置。
==只有key
值变化,React才会认为组件整体变化,整体渲染==
解决方法是给input
加上key
的属性,根据页面变化,强制重新渲染,然后在componentWillReceiveProps
里面对选中的答案状态进行重置
并且,不能在componentWillUpdate
和componentShouldUpdate
里面对state
的值进行控制,会造成死循环
(2017.07.14更新)
当时查资料的时候对这里理解的不全面,原文的意思是如果key
值太简单,例如只有数字序号作为key
,当项目发生变化,key
值可能不变,React可能会认为是同一个组件而不进行渲染。所以上面提到的:
只有
key
值变化,React才会认为组件整体变化,整体渲染
不够准确,应该==将key
值独一无二化,例如用ID
来标识key
值==,这样key
值不重复,就不会发生不渲染的情况。
(2019.01.22更新)
有两个问题:
(1)React中的key
并不要求全局唯一,因为key
的作用域是==当前列表内==,同一个列表内唯一即可,不同列表、不同组件间都不需要考虑这个问题。
(2)第二个是,key
值是加可以在包含input
的组件的(由于当时的组件划分的不合理所以只能加在input
上)。
当组件上没有key
时,传入的Prop
发生变化,React会寻求复用,保存组件状态,不会触发Mount
系列事件,只会触发Update
系列事件;
而如果增加了key
,当传入的Prop
发生变化也会导致组件重新渲染,所有状态重置
因此就有两个处理方法:一个就是不为组件增加key
,而是在更新周期的componentWillReceiveProps
中寻求状态重置,另一种就是为组件增加key
,有React自动完成重置
前者在逻辑不复杂的情况下是可以使用的,但是如果逻辑比较复杂就会导致大量的逻辑和函数在componentWillReceiveProps
中,而后者就一劳永逸了,直接销毁了组件并重建,在componentDidMount
中处理重置好,可以参考这篇文章。
其实在Vue中也是相同的原理。
Prop
的类型验证和默认值
1 | export default class Question extends React.Component { |
对Mobx的Store中的变量赋值
1 | let { spendMinute } = timeStore; |
这样是不行的,这是声明了新的变量,有了两种方法:
(1)直接引用Store中的变量,这种方法在Mobx的严格模式下第一种方法是不允许的
1 | timeStore.spendMinute = 100 |
(2)引用Store中的方法,对变量赋值(推荐)
1 | // Store中 |
<div>
的blur
事件
会遇到这样的需求:当标签失去焦点时,将菜单隐藏并触发一些操作,直觉就想到使用focus
和blur
事件。
但是这两个事件只对form
表单控件有效,但是对于<span>
、<div>
等普通元素并不生效
解决方法就是设置这些元素的tabindex
属性,就可以触发焦点事件了。
1 | <div onBlur={this.blurHandler.bind(this)} |
此外,如果希望点击出现的菜单本身不会在点击自己时,因为blur
事件消失,需要将菜单放入被点击事项的子元素中。
DOM事件传参
DOM事件传参,事件对象作为最后一个参数并传入到事件处理程序中
1 | <button onClick={this.deleteRow.bind(this, id)}>Delete Row</button> |
与Vue中是不同的。
Vue中的点击事件传参时,需要手动将$event
传入,否则事件处理程序无法访问事件对象。不传参时,事件处理程序默认的参数就是事件对象。
React中的this
React组件中的this
都指向了组件本身,但是为了接受客户端的响应而添加的回调函数,直接添加到了window
对象上,再这个函数里面用到的this
就指向了window
而非React组件。
1 | export default class PCIndex extends React.Component { |
上面的setState
会报错(可怕的是当时使用了Mobx,直接对组件内的属性赋值this.updateCounter++
,没有报错而是直接无效)
解决方法有两个,一个是将组件的this
缓存下来:
1 | async markPhoto(index) { |
第二种方法就是改用箭头函数:
1 | async markPhoto(index) { |
对于this
的指向,一定要谨慎!
循环的问题
其实这个问题和React关系不大,还是自己太菜。
有这样的一段代码,要求根据数组成员的某些属性筛选数后,创建一个新的数组,当时的做法啊是:
1 | const questions = [ |
但由于间隔着遍历导致数组,导致结果会形成带有空位的数组,在后面处理的时候出现了bug
当时的改进方案是:
1 | const questions = [ |
但是现在来看,当时还是太菜,这就是没有code review的缘故,没人指出你的代码有多烂,只能靠自己回过头再看看,发现自己菜的一比。
可以直接用push
就行了(2018-11-22):
1 | questions.forEach((question, index) => { |
如果数据量不大(因为会遍历两次)的时候可以写成函数式的,更清晰(2019-01-22):
1 | let markParamAnswer = questions.filter(question => question.hasMarked).map(v => { |