为什么react选择了函数式组件(剖析原理)
注意:这篇文章,主要是剖析组件的初次渲染和重新渲染。所以,其它部分不要太较劲。
一、react类组件和函数式组件重新渲染时的区别
1、看现象:
1)、代码(demo01)
类组件:
// 1、类组件
class ComClass extends React.Component {
constructor(props) {
super();
this.props = props;
console.log("类组件的构造函数被调用了");
}
render() {
console.log("类组件的render被调用了");
return (
<div style={{ "backgroundColor": "pink" }}>
<h5 >我是类组件</h5>
<p>{this.props.name}</p>
</div>
);
}
}
函数式组件:
// 2、函数式组件
function ComFn(props) {
console.log("函数式组件的函数被调用了");
return (
<div style={{ "backgroundColor": "skyblue" }}>
<h5 >我是函数式组件</h5>
<p>{props.name}</p>
</div>
);
}
使用组件:
let name = "张三疯";
function changeName() {
name = "张四疯"
renderDom();
}
function renderDom() {
ReactDOM.render(
<div>
<button onClick={changeName} >修改</button>
<ComClass name={name} /><br />
<ComFn name={name} />
</div>, document.getElementById("box"));
}
renderDom();
2)、运行:
2.1)、初次运行时,我们发现在控制台中打印的内容为:
类组件的构造函数被调用了
类组件的render被调用了
函数式组件的函数被调用了
2.2)、当点击“修改”按钮时,我们发现控制台中打印的内容为:
类组件的render被调用了
函数式组件的函数被调用了
3)、总结(敲黑板,重点):
1、类组件重新渲染时,只调用了render
2、函数式组件重新渲染时,会调用函数整个本身(哈哈,不调用它也不行啊)
2、看原理:
1)、用原生的方式剖析:
函数式组件的剖析:
标签的方式使用函数式组件:
<ComFn name={name} />
基本上等价于:
{ComFn({name})} //组件的方式使用,就是在调用函数
类组件的剖析:
标签的方式使用类组件:
<ComClass name={name} /><br/>
等价于
{new ComClass({name}).render()}
但是
这样改了后,初次渲染没有问题。当点击了“修改”按钮时,不一样了。
所以,类组件里,应该是把new ComClass({name})实例化出来的对象,记录起来了。所以,应该等价于
1、定义一个对象,保存了new ComClass({name})
//在react里对类组件对象进行了保存。
let comObj = new {new ComClass({name});
2、在渲染时,只是调用了类组件的render函数。
comObj.render();
即:最终代码变成了如下:
2)、代码(demo02):
类组件和函数式组件的代码不用改变。
使用组件
let name = "张三疯";
//此处保存了类组件的实例化对象(这个只是模拟实现,react内部并不是这么简单)
let comObj = new ComClass({name});
function changeName(){
name = "张四疯";
comObj.props = {name}
renderDom();
}
function renderDom(){
ReactDOM.render(
<div>
<button onClick={changeName} >修改数据</button>
{/*此处用原生的方式使用组件*/}
{comObj.render()}
{ComFn({name})}
</div>, document.getElementById("box"));
}
renderDom();
3)、运行:
3.1)、初次运行时,我们发现在控制台中打印的内容为:
类组件的构造函数被调用了
类组件的render被调用了
函数式组件的函数被调用了
3.2)、当点击“修改”按钮时,我们发现控制台中打印的内容为:
类组件的render被调用了
函数式组件的函数被调用了
运行结果和组件的方式一样。
二、看看组件里使用定时器,并且,重新渲染组件
1、看现象
1)、代码(demo03):
类组件:
// 1、类组件
class ComClass extends React.Component {
constructor(props) {
super();
this.props = props;
console.log("类组件的构造函数被调用了");
}
showMessage = () => {
//在显示props时(通过this访问props),props里的内容被改变了。
console.log('类组件: ' + this.props.name);
};
handleClick = () => {
// 分析问题:
// 1、3秒钟后调用函数(通过 this 的方式调用)
setTimeout(this.showMessage, 3000);
};
render() {
console.log("类组件的render被调用了");
return (
<div style={{ "backgroundColor": "pink" }}>
<h5 >我是类组件</h5>
<p>name:{this.props.name}</p>
<input type="button" value="调用带着定时器的函数" onClick={this.handleClick} />
</div>
);
}
}
函数式组件:
// 2、函数式组件
function ComFn(props) {
console.log("函数式组件的函数被调用了");
//这个是闭包:
// 每调用一次父函数(ComFn),都会重新定义一个新的子函数。新的函数中保存着父函数新的形参
const showMessage = () => {
console.log('函数式组件: ' + props.name);
};
//这个是闭包:
//每调用一次父函数(ComFn),都会重新定义一个新的子函数。新的函数中保存着父函数新的形参
const handleClick = () => {
setTimeout(showMessage, 3000);
};
return (
<div style={{ "backgroundColor": "skyblue" }}>
<h5 >我是函数式组件</h5>
<p>name:{props.name}</p>
{/*先点击这个按钮,调用,第一次定义的 showMessage和handleClick*/}
<input type="button" value="调用带着定时器的函数" onClick={handleClick} />
</div>
);
}
使用组件:
let name = "张三疯";
function changeName() {
name = "张四疯"
renderDom();
}
function renderDom() {
ReactDOM.render(
<div>
<button onClick={changeName} >修改</button>
<ComClass name={name} /><br />
<ComFn name={name} />
</div>, document.getElementById("box"));
}
renderDom();

2)、运行:
2.1)、类组件:
点击类组件的“调用带着定时器的函数” 按钮后,再点击”修改“按钮(注意先后顺序)。我们发现了:界面上打印的和控制台打印的是一样的。这是不对的:因为,我点击“调用带着定时器的函数” 按钮时,name的值是”张三疯“,应该打印”张三疯“

2.2)、函数式组件:
点击类组件的“调用带着定时器的函数” 按钮后,再点击”修改“按钮(注意先后顺序)。我们发现了:界面上打印的和控制台里打印的不一样,这是对的:因为,我点击“调用带着定时器的函数” 按钮时,name的值是”张三疯“,应该打印”张三疯“

3)、总结
原因何在?以下文字要细细的品,如果品不出来,就结合上面的代码,再看看。
类组件:
当类组件重新渲染时,只调用了render函数。组件的this不变。等定时器到了时,读取属性的的值时,先通过this找到props。再通过props找到name。而此时,name的值,已经发生了变化。所以,自然读到的是新值“张四疯” ,这个应该好理解。
函数式组件:
(你必须对闭包是清楚的),当函数式组件重新渲染时,调用了函数(组件),那么在函数式组件里的 函数(showMessage,handleClick)就会被重新定义,新定义的函数保存着父函数(组件)的新的形参和局部变量。而我们点击“调用带着定时器的函数”时,调用的是 第一次定义的showMessage,handleClick(这两个函数里保存了父函数(组件)第一次传入的形参和局部变量)。
其实,上面的代码中,已经做了注释。我再次解释后,如果不明白的话,看看下面的剖析原理的代码。
2、看原理:
1)、用原生的方式剖析
与第一大点的第二小点的“剖析原理”一样:
函数式组件的剖析:
标签的方式使用函数式组件:
<ComFn name={name} />
基本上等价于:
{ComFn({name})} //组件的方式使用,就是在调用函数
类组件的剖析:
把new ComClass({name})实例化出来的对象,记录起来了。
1、定义一个对象,保存了new ComClass({name})
//在react里对类组件对象进行了保存。
let comObj = new {new ComClass({name});
2、在渲染时,只是调用了类组件的render函数。
comObj.render();
2)、代码(demo04):
类组件和函数式组件的代码不用变(同第二大点的第一小点:组件里使用定时器,并重新渲染组件)。
使用组件:
这个代码等同于第一大点的第二小点。
let name = "张三疯";
let comObj = new ComClass({ name });
function changeName() {
name = "张四疯";
comObj.props = { name }
renderDom();
}
function renderDom() {
ReactDOM.render(
<div>
<button onClick={changeName} >修改</button>
{comObj.render()}
{ComFn({name})}
</div>, document.getElementById("box"));
}
renderDom();
三、为什么react现在更推荐函数式组件
React的核心理念之一就是,界面应当是数据的不同形式的简单投影。相同的输入应该产生相同的输出。而函数式组件的写法,使用闭包的特性,显然符合这一理念:每个闭包里保存在父函数的当前形参(props)和局部变量。而类组件里,由于,每次读取数据,要根据this指针去读取,那必然不会读取到属于自己当前状态的值。而是更新后的最新的值。
四、补充:类组件如何解决以上问题呢:
其实还是利用了闭包。
看类组件的代码修改(demo05):
//修改类组件里的showMessage 和 handleClick 函数。
showMessage = (name) => {
console.log('类组件: ' + name);
};
handleClick = () => {
let name = this.props.name;
//此处:
//1 、()=>this.showMessage(name)是闭包。父函数是handleClick,
//2、闭包会把当前的局部变量name持有,然后,调用 showMessage(name)时,把name的值传入showMessge里
setTimeout(()=>this.showMessage(name), 3000);
};
五、类组件和函数式组件区别:
在react的模式和开发思维上,函数组件和类组件还是有区别的。
1、各自的特点:
1)、类的组件主要是面向对象编程,是建立在继承之上,它的生命周期等核心概念的特点
2)、函数组件主要是函数式编程,无副作用,并且在引用的时候透明的特点
2、使用场景:
因为两者主打的特点不一样,所以在使用场景上自然就存在一些差异了:
1)、如果组件依赖生命周期,并且它的设计上需要继承的特性,我们在选择上更倾向类组件会更合适一点
2)、由于HOOK的推出,逐渐降低了生命周期的概念,那么类组件可以更好的取代函数组件的开发,而且官方也推崇“组合优于继承”的思想,所以类组件的优势也在慢慢的淡出。

浙公网安备 33010602011771号