作者:掘金–全栈艺术家
地址:https://zhuanlan.zhihu.com/p/348069918
当使用React框架开发的时候,有两种方式创建组件,使用函数和使用类,目前函数组件越来越流行。本文将通过举例的方式,分析函数组件和类组件的不同,带你深入探索React的世界。
函数组件和类组件处理JSX的方式不同,就像他们的名字,函数组件是一个纯Javascript函数,直接返回JSX;而类组件是一个Javascript类,通过扩展 React.Component ,并实现render方法,render方法中返回JSX。下面举例说明:
import React from "react";
const FunctionalComponent = () => {
return <h1>Hello, world</h1>;
};
上面通过ES6箭头函数的形式定义了一个函数组件,函数体内直接返回JSX。如果你对箭头函数不熟悉,也可以写成下面这种形式:
import React from "react";
function FunctionalComponent() {
return <h1>Hello, world</h1>;
}
两种写法是一样的。
然后,来看看如何定义类组件,首先我们需要扩展 React.Component ,然后在render方法中返回JSX,具体看下面的代码片段:
import React, { Component } from "react";
class ClassComponent extends Component {
render() {
return <h1>Hello, world</h1>;
}
}
上面使用了ES6的解构赋值语法来导入模块,如果你对解构赋值语法不熟悉,也可以写成下面这种形式,会看上去更简洁一些:
import React from "react";
class ClassComponent extends React.Component {
render() {
return <h1>Hello, world</h1>;
}
}
当需要向一个组件传递数据的时候,我们使用props,比如 < FunctionalComponent name=“Shiori” / > ,name就是Component的一个props属性,这里可以有更多属性。FunctionalComponent组件的函数形式的定义如下:
const FunctionalComponent = ({ name }) => {
return <h1>Hello, {name}</h1>;
};
或者不使用解构赋值
const FunctionalComponent = (props) => {
return <h1>Hello, {props.name}</h1>;
};
这种方式,你需要使用props.name来获取name属性。
然后,我们来看看类组件如何使用props,
class ClassComponent extends React.Component {
render() {
const { name } = this.props;
return <h1>Hello, { name }</h1>;
}
}
在类组件中,你需要使用this来获取props,然后可以使用解构赋值获取name属性。
在React项目中,我们不可避免的要处理状态变量。类组件直到最近才支持处理状态,然而,从React从16.8版本开始,函数组件支持钩子方法 useState ,这样我们可以很方便的在函数组件中使用状态变量。下面通过一个counter计数器实例来说明它们的不同。
在函数组件中处理状态变量
const FunctionalComponent = () => {
const [count, setCount] = React.useState(0);
return (
<div>
<p>count: {count}</p>
<button onClick={() => setCount(count + 1)}>Click</button>
</div>
);
};
这里使用 useState 钩子,它接收一个初始的state作为参数。在本例中,计数器从0开始,所以我们给count一个初始值0。
state的初始值支持各种数据类型,包括null,string或者object对象,只要是javascript允许的都可以。在=号的左边,我们使用解构赋值的形式来接受useState的返回值,包括当前的状态变量和更新该变量的setter函数,即 count 和 setCount。
class ClassComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<p>count: {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click
</button>
</div>
);
}
}
与函数组件大同小异,首先我们要理解React.Component的构造函数constructor,react的官方文档对constructor的定义如下:
“The constructor for a React component is called before it is mounted. When implementing the constructor for a React.Component subclass, you should call super(props) before any other statement. Otherwise, this.props will be undefined in the constructor, which can lead to bugs.”
翻译一下,
React组件的constructor方法会在组件完全加载完成之前调用。在constructor方法中,你需要在第一行调用super(props),否则会报this.props是undefined的错误。
如果在类组件中,你没有实现constructor方法并调用super(props),那么所有的状态变量都将是undefined。所以,别忘记先定义constructor方法,在constructor方法中,我们需要给this.state一个初始值,像上面的代码那样。然后我们可以在JSX中使用 this.state.count 来获取count的值,setter的使用也是类似的。
这里先定义一个onClick方法,后面会用到,
onClick={() =>
this.setState((state) => {
return { count: state.count + 1 };
})
}
这里注意setState()方法接收的是个箭头函数,而箭头函数的参数是state和props,props是可选的,这里没用到就没写。
React的组件在它整个的渲染的过程中,有它的生命周期。如果你之前一直使用类组件,刚刚接触函数组件,你可能会疑惑,为什么在函数组件中没有 componentDidMount() 这类的生命周期方法?但是别急,有其他的钩子函数可以使用。
类组件的生命周期函数componentDidMount会在首次渲染完成之后调用。首次渲染完成之前会调用componentWillMount ,但是这个方法在新版本的React中不推荐使用了。
在函数组件中,我们使用useEffect钩子函数来处理生命周期内的事件,像下面这样,
const FunctionalComponent = () => {
React.useEffect(() => {
console.log("Hello");
}, []);
return <h1>Hello, World</h1>;
};
useEffect有两个参数,第一个是箭头函数,第二个是 [ ] , [ ] 里面是变化的state(s)。什么意思呢?就是[]中的状态变化了,箭头函数会被调用。如果像现在这样写个 [ ],那箭头函数只会在组件第一次渲染之后调用一次,其功能类似下面类组件的componentDidMount。
class ClassComponent extends React.Component {
componentDidMount() {
console.log("Hello");
}
render() {
return <h1>Hello, World</h1>;
}
}
const FunctionalComponent = () => {
React.useEffect(() => {
return () => {
console.log("Bye");
};
}, []);
return <h1>Bye, World</h1>;
};
这里注意return的也是一个箭头函数,这个函数就是在卸载阶段执行的。当你需要执行一些卸载操作,可以放在这里,比如你可以把clearInterval放在这里,避免内存泄漏。使用useEffect钩子函数的最大好处就是可以把加载函数和卸载函数放在一个同一个地方。这里对比一下类组件的写法:
class ClassComponent extends React.Component {
componentWillUnmount() {
console.log("Bye");
}
render() {
return <h1>Bye, World</h1>;
}
}