React中函数组件和类组件的差异

2,948 阅读3分钟

关于react中的函数组件和类组件,相信网上有很多文章都有介绍过其中的差异,不过我在实际使用中发现了一些细微的差异,导致我个人认为类组件使用门槛较低,但逻辑相对复杂。使用函数组件门槛较高,但是能适当简化逻辑。由于我之前使用过vue,所以文中也会加入一些与vue的对比。

react属于mvvm框架,我们可以通过修改model(也就是react里面的state,vue2里面的data),触发框架(ViewModel),推动视图view的修改。

但是react中对于state的修改,是不能直接修改的,而是要通过react的api去修改,这里做一个比较。

修改model的方法
react 函数组件hook,通过调用useState这个hook,useState返回两个东西,第一个是model的值,第二个是修改model的函数,请注意对model的修改不是立即生效的,只能通过useEffect这个hook去监听model,当model变化时
react 类组件react框架内提供的this.setState函数 ,请注意对model的修改也不是立即生效的,但是该函数接受第二个参数,可以传入一个函数作为model修改完之后的回调
vue2this.$set,vue提供的对model的修改方法,修改后model立即改变,但是vue底层会将model标记为dirty,会在下一轮的渲染中计算本次修改带来的影响

(这里吐槽一下markdown的表格真难用)

这里不得不说一句vue的门槛真是太低了,由框架隐藏了很多脏活。

其实从上面可以看到vue和react的一个很大的不同点就是修改model的即时性。那么model修改的即时性代表着什么呢?

试想下面一个需求:相信大家都用过微博,微博我们可以通过不断的滚动来加载微博,也可以知道微博的加载肯定不是一次把全部数据取回来的,而是通过维护一个数组,根据用户的不断滚动更新数组里面的数据,因此需要在model里面维护一个weibos数组,一个pageNumber代表目前是第几次取微博,一个pageSize代表每次去服务器取多少条数据。

(为什么要维护到state里面呢?因为state里面的数据框架会保存起来,即使组件销毁重用也能保存起来,但是如果放在其他地方,组件销毁后值会变回初始值,这样是没办法实现不断加载的)

我相信大家还知道微博有一个功能,那就是双击底部栏的icon,返回最顶部的微博,并重新获取,这个需求其实就是把state里面的weibos数组清空,然后把offset清零,重新请求。

那么问题来了,这个需求用上面三种不同的方法,实现起来有什么区别呢? 我们来梳理一下上面的需求,就是

1、需要根据用户的滑动,不断更改pageNumber,向服务器发请求,获取微博后存入weibos数组。

2、当用户触发某个特定的动作时,要重置pageNumber和weibos数组,重新获取。

对于vue而言这两个需求很简单,对于第一个需求,只需要

function onTouchMove() {
  let that = this;
  ++this.pageNumberpost(url, {
      this.pageNumber,
      this.pageSize,
      ...otherParams
  }).then(res => {
      that.weibos.push(res);
  })
}

第二个需求的实现则是:

function onResetWeibos(){
  let that = this;
  this.weibos = [];
  this.pageNumber = 0;
  post(url, {
      this.pageNumber,
      this.pageSize,
      ...otherParams
  }).then(res => {
      that.weibos = res;
  })
}

这一段代码相当的简洁明了,对于第一个需求,递增pageNumber然后往服务器发请求,把请求到的数据放入weibos这个数组中。

第二个需求,清零pageNumber,然后往服务器发请求,直接把请求到的数据作为weibos的初始值。

name来看看react的类组件要如何实现 对于第一个需求

function onTouchMove(){
	let that = this;
	this.setState({
    	pageNumber: pageNumber + 1,
    }, () => {
    	post(url, {
        	that.pageNumber,
            that.pageSize,
            ...otherParams,
        }).then(res => {
        	this.setState({
            	...this.weibos,
                ...res,
            });
        });
    });
}

请注意这里对setState传入了回调函数,在回调里面才对服务器发起请求,这就是因为react对model(在react里面叫state)的修改是有滞后性的,所以如果调用了setState之后马上读取,读到的会是旧值,怎么理解呢?看下面一段代码

consturctor() {
	this.state = {
    	name: "Rango",
    };
}

this.setState({
	name: "cold",
}, () => {
	console.log(`callback: ${this.state.name}`);
});
console.log(`after setState: ${this.state.name}`);

这段代码的输出是 after setStat: Rango callback: cold

虽然我们已经通过setState通知react去修改model了,但是react并不会马上修改,所以如果直接在setState之后读取model,读到的是修改之前的值。

对于第二个需求,react的类组件的实现则是

function onResetWeibos(){
	this.setState({
    	weibos: [],
        pageNumber: 0,
    }, () => {
    	post(url, {
        	this.pageNumber,
            this.pageSize,
        }).then((res) => {
        	this.weibos = res;
        })
    })
}

通过setState的回调,实现了这个对weibos数组清零重新获取的需求。

那么来看使用react的函数组件时,会发生什么?

由于函数组件的逻辑一般都写在一起,所以两个需求都写一起

function Weibo(props) {
	let pageSize = 10;
	let [pageNumber, setPageNumber] = useState(0);
    let [weibos, setWeibos] = useState([]);
    
	useEffect(() => {
    	post(url, {
        	offset,
            pageSize,
        }).then(res => {
        	if (offset === 0){
            	setWeibos(res);
            }else {
            	setWeibos([...weibos, ...res]);
            }
        })
    }, [pageNumber])
    
    onTouchMove(){
    	setPageNumber(pageNumber + 1);
    }
    
    onResetWeibos() {
    	setPageNumber(0);
    }
}

不得不说使用hook之后的代码看起来真是简单很多,因为hook对于model的修改监听只能通过useEffect这个钩子,这就像是vue的watch一样,但是对于被监听的model全部修改都会被触发,所以在使用useEffect时要完全考虑到model的各种变化,并做好处理,当model变得复杂时hook的逻辑也会更加重,更加难以理解,所以hook让我们代码变得简洁的同时,也对逻辑能力有了更高的要求。

(听说vue3中引入了composition API,就像是react的hook,不过已经脱坑了就没有再研究)

以上就是对于react的model修改的一些总结以及和vue的对比,这就是本周的技术博客,接下来该考虑下周的内容了。

2021.02.02

经过五个月的工作,我发现实际开发中有这样一些需求,在某种情况下,一个字段的变化要触发某一个逻辑,但是某种情况下,该字段的变化不需要触发对应的逻辑。也就是条件式的监听某一个变量的变化。这个时候可以通过useState另外加一个flag,建立对flag的监听,当需要触发逻辑的时候就顺便把flag改变,这样就可以实现条件式的监听。具体的例子我还没有想好,等想到恰当的例子会补充一下。