React状态 和 JavaScript箭头函数
React状态 和 JavaScript箭头函数
在看 React 的状态时见到了 JS 的箭头函数,一时间没看明白。
React 状态
React 把组件看成是一个状态机(State Machines)。通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。
在 React 中,当一个组件的状态发生改变时,React 会感知到状态的变化,并将其更新到 DOM 中。
先看一个界面上时钟的实现:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>现在是 {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('example')
);
这个时钟只通过 React 实现了控件的绑定,但还没有实时刷新的功能,所以,需要在代码中设置一个定时器使其能够每秒刷新一次,此处使用生命周期方法(钩子)实现:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>现在是 {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('example')
);
其中 componentDidMount() 与 componentWillUnmount() 就是生命周期方法(钩子)。
当这个 Clock 组件输出到 DOM 后会执行 componentDidMount() 钩子,相当于组件的初始化,在上面,我们在这个方法中设置了一个定时器。
当这个 Clock 组件从 DOM 中移除后,会执行 componentWillUnmount() 钩子,通过初始化时设置的定时器的 ID this.timerID,可以卸载这个定时器。
在设置的定时器中执行了 tick() 方法,这个方法在执行时会设置 Clock 组件的状态,将 data 属性设置为当前的新的 Date 对象,React 可以感知到 State 的改变,会调用 render() 方法重新渲染输出,且 React 可以对比前后内容的差距,只重新渲染有区别的内容,这样就实现了画面的更新。
JavaScript箭头函数
在上面的例子中,可以注意到在 componentWillUnmount() 方法中使用了箭头函数:
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
其中,setInterval(func, time) 方法为每过 time 就执行一次 func,在上面的例子中,就是每过 1000 毫秒(1秒)就执行一次 () => this.tick() 方法。
this.tick()
那为什么这里要使用箭头函数呢?我一开始的想法是这里只是为了装B,于是把它换掉了:
componentDidMount() {
this.timerID = setInterval(
this.tick(),
1000
);
}
打开页面,时间显示了,但不会动了。后面发现,此时将 this.tick() 换为 console.log(this),此时的 this 确实是 Clock,但这个操作相当于将 tick() 的返回值设定为了 setInterval 的第一个参数,当然是不会动了。
this.tick
于是尝试直接将 tick 设定为 setInterval 的第一个参数:
componentDidMount() {
this.timerID = setInterval(
this.tick,
1000
);
}
打开页面,也是一样的问题,时间不会动,同时控制台还报错了:
Uncaught TypeError TypeError: this.setState is not a function
此处就有点难以理解了,为什么访问不到 setState 方法呢。在问了GPT后得到的答案是:
在 JavaScript 中,this 的值取决于函数的调用上下文。当您在一个函数内部使用 this 关键字时,它将被设置为当前函数的调用上下文,也就是函数被调用时所处的环境。这意味着,this 的值可能会随着函数的调用方式和位置而发生变化。如果在 componentDidMount 方法中使用 this.tick,您可能需要在 constructor 方法中将 tick 方法绑定到组件实例上。否则,在调用 this.tick 时,this 上下文可能会被设置为全局对象,而不是组件实例。
简单理解,大概就是 tick 被调用时,里面的 this 指向的已经不是 Clock 了,可以验证一下,在 tick 中输出一下 this 看看:
tick() {
console.log('tcik this' + this)
this.setState({
date: new Date()
});
}
控制台输出:tcik this[object Window],可见 this 确实是指歪了。
function()
再次尝试,这次使用匿名函数 function() 将 tick() 包装起来:
componentDidMount() {
this.timerID = setInterval(function(){
this.tick();
},1000
);
}
这次也报错了,问题是 tick() 不是一个方法:
Uncaught TypeError TypeError: this.tick is not a function
此时我就无法理解了。在研究了半天后,通过添加 console.log(this) 输出 this 发现,此时的 this 居然是 Window:
componentDidMount() {
this.timerID = setInterval(function(){
console.log(this);
this.tick();
},1000
);
}
// 控制台输出:
// Window {window: Window, self: Window, document: #document, name: '', location: Location, …}
// Uncaught TypeError TypeError: this.tick is not a function
在这个方法中,this 指代的是 Window,而 tick() 是 Clock 的方法,所以这个报错就很合理了。
查了查资料发现,匿名函数中的 this 指代的就是 Window,所以直接使用匿名函数是行不通的,除非在外部保存 Clock 对象,再将对 this 的调用改为对 Clock 对象的调用:
componentDidMount() {
let that = this;
this.timerID = setInterval(function(){
console.log(that);
that.tick();
},1000
);
}
// 控制台输出:
// Clock {props: {…}, context: {…}, refs: {…}, updater: {…}, state: {…}, …}
这下就正常了,通过创建 that 保存正确的 Clock 对象,成功获取到了 tick 方法。但这一会 that,一会 this 的,确实有点麻烦和乱了。所以这也是箭头函数存在的意义了。
() => this.tick()
箭头函数是 ES6 的新特性,箭头函数表达式的语法比普通函数表达式更简洁:
(参数1, 参数2, …, 参数N) => { 函数声明 }
(参数1, 参数2, …, 参数N) => 表达式(单一)
// 相当于:(参数1, 参数2, …, 参数N) =>{ return 表达式; }
没有参数的函数应该写成一对圆括号: () =>
箭头函数的特点有两个:更简短的函数并且不绑定 this。在最初的代码中用到了箭头函数:
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
与上面使用 function() 创建一个匿名函数不同,匿名函数中的 this 被绑定(指向)到 window,因此直接在匿名函数中使用 this 时,上下文环境基本都是错误的。但在箭头函数中,不会将 this 绑定到 window,而是将 this 上下文绑定到当前组件实例:
// 直接使用 tick,this -> Clock,但 tick 中的 this 指向了 Window
this.tick
// 使用匿名函数,this -> Window
function(){ this.tick() }
// 使用箭头函数,this -> Clock,且 tick 中的 this 也指向 Clock
() => this.tick()
可以理解为,箭头函数中的 this 上下文和箭头函数是相同的,而匿名函数中的 this 指向了 Window:
-Window
--Clock
---this
---() => this.tick() 中的 this
--funtion(){ this.tick() } 匿名函数中的 this
还是有点绕的,JS虽然好使,但一开始看不懂的时候还要看一堆乱七八糟的语法,累。

浙公网安备 33010602011771号