functionApp(props){constshowMessage=()=>{alert('Hello'+props.user);};consthandleClick=()=>{setTimeout(showMessage,3000);};return(
那么我们用类应该怎么写这个组件呢?一个简单的重构可能就象这样:
classAppextendsReact.Component{showMessage=()=>{alert('Hello'+this.props.user);};handleClick=()=>{setTimeout(this.showMessage,3000);};render(){return
下面我们新建一个react项目,在src下新建两个组件,一个classComponent组件,一个是functionComponent组件。代码就是上面我们写的这两个组件,只不过内容稍有区别:
classComponent:
importReactfrom'react';classProfilePageextendsReact.Component{showMessage=()=>{alert('你选择了'+this.props.user);};handleClick=()=>{setTimeout(this.showMessage,3000);};render(){return
importReactfrom'react';functionProfilePage(props){constshowMessage=()=>{alert('你选择了'+props.user);};consthandleClick=()=>{setTimeout(showMessage,3000);};return(
importReactfrom"react";importReactDOMfrom"react-dom";importProfilePageFunctionfrom'./functionComponent';importProfilePageClassfrom'./classComponent';exportdefaultclassAppextendsReact.Component{state={user:'小杰',};render(){return(<>
欢迎来到{this.state.user}的家!
当我们单击上面的按钮时,执行的就是函数式组件,点击下面的按钮时,执行的就是类。如果按照我们以往的思路,他们二者都会有相同的结果,但事实真的如此吗?
我们按照下面的顺序执行:
1.点击函数式组件按钮
2.在点击后立刻切换想要拜访的朋友
函数式组件的执行结果如下:
页面弹出的还是我们当时选择的值
同样的操作我们再试一下类组件:
现在页面弹出的就是我们实时更改的值了。
在这个例子中,第一个行为是正确的。因为最开始我选择要拜访小杰点击了确定发出了命令,然后我再切换到小尚,但是我并没有点击确定,我的组件不应该混淆我要拜访的人。在这里,类组件的实现很明显是错误的。
所以为什么我们的例子中类组件会有这样的表现?
让我们来仔细看看我们类组件中的方法:showMessage
showMessage=()=>{alert('你选择了'+this.props.user);};这个类方法从中读取数据。在React中Props是不可变的,所以他们永远不会改变。然而,this是,而且永远是,可变的。
我们的组件属于一个拥有特定props和state的特定渲染。
然而,调用一个回调函数读取的timeout会打断这种关联。我们的回调并没有与任何一个特定的渲染绑定在一起,所以它失去了正确的props。
我们想要以某种方式“修复”拥有正确props的渲染与读取这些props的回调之间的联系。它们在类的某个地方被弄丢了。
一种方法是在调用事件之前读取,然后将他们显式地传递到timeout回调函数中去:
然而,如果我们能利用JavaScript闭包的话问题将迎刃而解。
通常来说我们会避免使用闭包,但是在React中,props和state是不可变的,这就消除了闭包的一个主要缺陷。
这就意味着如果你在一次特定的渲染中捕获那一次渲染所用的props或者state,你会发现他们总是会保持一致,就如同你的预期那样。
classProfilePageextendsReact.Component{render(){constprops=this.props;constshowMessage=()=>{alert('你选择了'+props.user);};consthandleClick=()=>{setTimeout(showMessage,3000);};return
所以这个时候我们就明白了函数式组件和类组件的区别:
functionProfilePage({user}){constshowMessage=()=>{alert('Followed'+user);};consthandleClick=()=>{setTimeout(showMessage,3000);};return(
使用Hooks,同样的原则也适用于状态。看这个例子:
functionMessageThread(){const[message,setMessage]=useState('');constshowMessage=()=>{alert('Yousaid:'+message);};consthandleSendClick=()=>{setTimeout(showMessage,3000);};consthandleMessageChange=(e)=>{setMessage(e.target.value);};return(<>
因此我们知道,在默认情况下React中的函数会捕获props和state。但是如果我们想要读取并不属于这一次特定渲染的,最新的props和state呢?
在函数式组件中,你也可以拥有一个在所有的组件渲染帧中共享的可变变量。它被成为“ref”:
functionMyComponent(){constref=useRef(null);//你可以通过ref.current来获取保存的值.//...}在很多情况下,你并不需要它们,并且分配它们将是一种浪费。但是,如果你愿意,你可以这样手动地来追踪这些值:
functionMessageThread(){const[message,setMessage]=useState('');constlatestMessage=useRef('');constshowMessage=()=>{alert('Yousaid:'+latestMessage.current);};consthandleSendClick=()=>{setTimeout(showMessage,3000);};consthandleMessageChange=(e)=>{setMessage(e.target.value);latestMessage.current=e.target.value;};如果我们在state中读取,我们将得到在我们按下发送按钮那一刻的信息。但是当我们通过ref读取时,我们将得到最新的值,即使我们在按下发送按钮后继续输入。
通常情况下,你应该避免在渲染期间读取或者设置refs,因为它们是可变得。我们希望保持渲染的可预测性。然而,如果我们想要特定props或者state的最新值,那么手动更新ref会有些烦人。我们可以通过使用一个effect来自动化实现它:
functionMessageThread(){const[message,setMessage]=useState('');//保持追踪最新的值。constlatestMessage=useRef('');useEffect(()=>{latestMessage.current=message;});constshowMessage=()=>{alert('Yousaid:'+latestMessage.current);};正如我们上面看到的,闭包实际上帮我们解决了很难注意到的细微问题。同样,它们也使得在并发模式下能更轻松地编写能够正确运行的代码。这是可行的,因为组件内部的逻辑在渲染它时捕获并包含了正确的props和state。
React函数总是捕获他们的值——现在我们也知道这是为什么了。