React提高01 遇到的坑

总结整理了一下一年前使用React开发测评平台时的经验。
总共使用React开发了一个项目,用了1个多月,学到的东西比这一年都多,值得好好总结。
已同步到个人博客,欢迎访问
定时任务中的setState
如果定时任务触发时,组件已经被销毁,会给出警告
setState(...): Can only update a mounted or mounting component.虽然只是一个warning,但是还是证明写的代码不规范,不一定什么时候就埋坑了。
这个问题实质是:setState在异步的callback里执行,而这个时候由于返回上一页,组件已经被销毁了。
用isMounted方法做判断是官方不推荐的方法,而且我也不知道怎么实现。
真正的解决方法应该是在componentWillUnmount中将事件清除或者变量设置为null。
对于setInterval来说需要在componentWillUnmount中clear
bind在addEventListener中的使用
在组件中添加了scroll事件:
window.addEventListener('scroll', this.windowScroll.bind(this));在componentWillUnmount中想要清除绑定的事件:
window.removeEventListener('scroll', this.windowScroll.bind(this));这样做是不会生效的。
因为bind(this)方法总会返回一个新的函数,所以在removeEventListener时,移除的是一个不存在的、新的函数。
解决方法是在constructor里面对windowScroll一次性绑定this。(这样this.windowScroll变量指向的就是同一个bind之后的方法了。
export default class Overview extends React.Component { constructor(props) {
super(props);
this.windowScroll = this.windowScroll.bind(this)
}
windowScroll(e) {
// do something
}
componentDidMount() {
window.addEventListener('scroll', this.windowScroll);
}
componentWillUnmount() {
window.removeEventListener('scroll', this.windowScroll);
}
// ...
}
globalStore中的方法在组件中不能直接调用
const { globalStore } = this.props;const { setBtnStatus } = globalStore;
globalStore.setBtnStatus(); // OK
setBtnStatus(); // 报错
这是因为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的类型验证和默认值
export default class Question extends React.Component { // 类型验证
static propTypes = {
finishBtnDisabled: PropTypes.bool
};
// Prop默认值
static defaultProps = {
finishBtnDisabled : false,
};
}
对Mobx的Store中的变量赋值
let { spendMinute } = timeStore;spendMinute = 100;
这样是不行的,这是声明了新的变量,有了两种方法:
(1)直接引用Store中的变量,这种方法在Mobx的严格模式下第一种方法是不允许的
timeStore.spendMinute = 100(2)引用Store中的方法,对变量赋值(推荐)
// Store中export default class Mark {
@observable spendMinute = 0;
@action changeSpendMinute(minute) {
this.spendMinute = minute
}
}
// 组件中
@observer
export default class Overview extends React.Component {
changeMinute() {
const { globalStore } = this.props;
globalStore.changeSpendMinute(100);
}
}
<div>的blur事件
会遇到这样的需求:当标签失去焦点时,将菜单隐藏并触发一些操作,直觉就想到使用focus和blur事件。
但是这两个事件只对form表单控件有效,但是对于<span>、<div>等普通元素并不生效
解决方法就是设置这些元素的tabindex属性,就可以触发焦点事件了。
<div onBlur={this.blurHandler.bind(this)} onFocus={this.focusHandler.bind(this)}
tabIndex="1">
blur
</div>
此外,如果希望点击出现的菜单本身不会在点击自己时,因为blur事件消失,需要将菜单放入被点击事项的子元素中。
DOM事件传参
DOM事件传参,事件对象作为最后一个参数并传入到事件处理程序中
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>与Vue中是不同的。
Vue中的点击事件传参时,需要手动将$event传入,否则事件处理程序无法访问事件对象。不传参时,事件处理程序默认的参数就是事件对象。
React中的this
React组件中的this都指向了组件本身,但是为了接受客户端的响应而添加的回调函数,直接添加到了window对象上,再这个函数里面用到的this就指向了window而非React组件。
export default class PCIndex extends React.Component { constructor(props) {
super(props);
this.state = {
updateCounter: 0,
}
};
async markPhoto(index) {
// ...
window.teacherMark = async function(result) {
// ...
this.setState({
updateCounter: (++this.state.updateCounter)
});
};
}
上面的setState会报错(可怕的是当时使用了Mobx,直接对组件内的属性赋值this.updateCounter++,没有报错而是直接无效)
解决方法有两个,一个是将组件的this缓存下来:
async markPhoto(index) { const self = this;
// ...
window.teacherMark = async function(result) {
// ...
self.setState({
updateCounter: (++this.state.updateCounter)
});
};
}
第二种方法就是改用箭头函数:
async markPhoto(index) { const self = this;
// ...
window.teacherMark = result => {
// ...
self.setState({
updateCounter: (++this.state.updateCounter)
});
};
}
对于this的指向,一定要谨慎!
循环的问题
其实这个问题和React关系不大,还是自己太菜。
有这样的一段代码,要求根据数组成员的某些属性筛选数后,创建一个新的数组,当时的做法啊是:
const questions = [ { hasMarked: true},
{ hasMarked: false},
{ hasMarked: true}
];
let markParamAnswer = [];
questions.map((question, index) => {
if (question.hasMarked) {
markParamAnswer[index] = {
"answerId": "123",
"teacherName": "123",
"score": -1,
"comment": "111111"
};
}
});
console.log(markParamAnswer[1]); // undefined
console.log(markParamAnswer); // [{...}, empty, {...}]
但由于间隔着遍历导致数组,导致结果会形成带有空位的数组,在后面处理的时候出现了bug
当时的改进方案是:
const questions = [ { hasMarked: true},
{ hasMarked: false},
{ hasMarked: true}
];
let markParamAnswer = [];
let i = 0;
questions.map((question, index) => {
if (question.hasMarked) {
markParamAnswer[index] = {
"answerId": "123",
"teacherName": "123",
"score": -1,
"comment": "111111"
};
i++
}
});
console.log(markParamAnswer); // [{...}, {...}]
但是现在来看,当时还是太菜,这就是没有code review的缘故,没人指出你的代码有多烂,只能靠自己回过头再看看,发现自己菜的一比。
可以直接用push就行了(2018-11-22):
questions.forEach((question, index) => { if (question.hasMarked) {
markParamAnswer.push({
"answerId": "123",
"teacherName": "123",
"score": -1,
"comment": "111111"
});
}
});
如果数据量不大(因为会遍历两次)的时候可以写成函数式的,更清晰(2019-01-22):
let markParamAnswer = questions.filter(question => question.hasMarked).map(v => { return {
"answerId": "123",
"teacherName": "123",
"score": -1,
"comment": "111111"
}
});
参考
- React setState can only update a mounted or mounting [email protected]
- react native Warning: setState(…): Can only update a mounted or mounting [email protected]
- React Checkbox Stays Checked Even After Component is [email protected]
- React 实践心得:key 属性的原理和用法
以上是 React提高01 遇到的坑 的全部内容, 来源链接: utcz.com/z/381856.html
