函数组件与类组件对比

1,031 阅读3分钟

本文作者:布鲁托

1. 函数组件

hook是可以给函数组件中添加了state和副作用的机制,可以不编写class组件使用react提供的特性。

一个简单的计算器例子:

import React, { useState } from 'react';

function App() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

在函数组件内使用了useState为函数组件引入了state,按钮点击后state更新,组件重新渲染最新的state。这个useState就是官方提供的hook,为了实现class组件中的功能,官方还提供了useEffect,useContext等。

2. 对比类组件

举一个当前很常见的组件:

  1. 进入当前组件(页面)开始请求api;

  2. 处理数据:统计、过滤或者重新聚合;

  3. 副作用:更新图表之类的;

  4. 事件监听与取消;

  5. 根据props更新组件。

2.1 componentDidMount

在组件挂载完成后执行一次。

类组件:

class Example extends Component {
  constructor(props) {
    super(props);
    this.state = {
      originData = [];
    }
  }

	componentDidMount() {
    API().then(res => {
      this.setState({
        originData: res
      });
    })
  }

	render() {
    return (
    //...
    )
  }
}

函数组件:

function Example(props) {
  const [originData, setOriginData] = useState([]);

  useEffect(() => {
    API().then(res => {
      setOriginData(res);
    });
  }, []);

  return (
  // ...
  )
}

在class组件中componentDidMount很清楚的标注了这个函数中的代码将在组件挂载完成后执行一次;函数组件中实现这样的功能是给useEffect加上第二个参数,一个空数组来实现。

2.2 对接口数据处理

类似于上面的例子,总之是要在请求成功后才可以操作。

2.3 执行副作用

类组件:

componentDidMount() {
  this.chart = createChart();
  this.chart.init();
}

componentDidUpdate(prevProps, prevState) {
  if(prevProps.active !== this.props.active) {
	  this.chart.update();    
  }
}

函数组件:

const exampleRef = useRef(null);

useEffect(() => {
	exampleRef.current = createChart();
  exampleRef.current.init();
}, [])

useEffect(() => {
  exampleRef.current.update();
}, [props.active]);

图表往往是依赖于一个实际的dom节点并在其上渲染的,因此初始化和更新必不可少。这个例子中,class组件实例会一直保存创建好的chart对象,可以很方便的在生命周期中使用;函数组件中如果用const变量保存,很明显每次组件更新都是一个新的chart对象,想要创建一个组件实例需要引入useRefhook,也许有更好的方式实现,但就这里来看还是支持class组件。

2.4 事件监听与取消

类组件:
handleResize = () => {
  // ...
}
componentDidMount() {
 	window.addEventListener('resize', this.handleResize);
}
componentWillUnmount() {
  window.removeEventListener('resize', this.handleResize);
}

函数组件:

useEffect(() => {
  const handleResize = () => {
    // ...
  }
  window.addEventListener('resize', this.handleResize);
  return () => {
    window.RemoveEventListener('resize', this.handleResize);
  }
}, []);

为了不影响其他组件,在本组件监听全局性的事件后,在组件销毁的时候应该取消监听。这个例子中hook的优势要明显一点,同一个事件handle的监听和取消在代码组织形式上放在了一起,看起来更紧凑。

2.5 prop更新

类组件

componetDidUpdate(prevProps) {
  if (prevProps.data !== this.props.data) {
    // 一些相关的更新操作
  }
}

函数组件:

useEffect(() => {
  // 一些相关的更新操作
}, [props.data]);

如果存在多个props属性更新需要执行一些副作用时,使用hook就无需写一长串比较运算。

3. 总结

无论使用函数式或Class,选择哪种取决于个人喜好,而且官方也没有计划会取消Class组件。需要指出的一点是,在编写图表组件时,使用Class组件会更胜一筹,因为this可以随时保存图表实例或者一些计算方法;而hooks需要使用useRef保存实例,而对于依赖于props的计算方法,用useMemouseCallback都会因props更新导致计算方法实例更新,对初始化造成影响,而且写法不够优雅。