函数式组件与类组件有何不同?
前言
React 中最关键的知识点就是 组件,在 React 16.8 之前(还没有 Hooks 前),我们的应用大多写成 Class 组件,因为 Class 组件有生命周期,能控制状态(state)。但函数式组件只能默默站在后面,说自己是木偶组件(也叫无状态组件),传来 props,展示 UI
以下文字都基于有了 Hooks 后
正文
函数式组件和类组件之间是否有什么根本上的区别?
函数式组件捕获渲染时的值
具体可以看这篇文章:函数式组件与类组件有何不同?
因为在 React 中 props 是不可变(immutable)的,它们永远不会改变。然而,this 是可变(mutable)的
事实上,这就是类组件 this 存在的意义。React 本身会随着时间的推移而改变,以便你可以在渲染方法以及生命周期方法中得到最新的实例
函数式组件会捕获当前状态下的值,如果你使用定时器改变当前值的状态,那函数式组件显示的还是原来的值,而不是最新值。而类组件会一直获取最新值
只要一渲染,函数式组件就会捕获当前的值。而类组件即使渲染了,但是它的 this 会指向最新的实例
类组件
可以看线上 Demo
class ClassDemo extends React.Component {
state = {
value: ""
};
showMessage = () => {
alert("最新值为 " + this.state.value);
};
handleMessageChange = (e) => {
this.setState({ value: e.target.value });
};
handleClick = () => {
setTimeout(this.showMessage, 3000);
};
render() {
return (
<div>
<input value={this.state.value} onChange={this.handleMessageChange} />
<button onClick={this.handleClick}>点击</button>
</div>
);
}
}
这样的结果是点击后获取到最新的值,而不是 3 秒前的值。为什么?因为 this 可变,3 秒之后执行 alert("最新值为 " + this.state.value)
。 this.state.value 指向最新的值
如果类组件如果想保存原来的值该怎么做?
一、调用事件之前读取 this.props
可以看线上 Demo
showMessage = (value) => {
alert("最新值为 " + value);
};
handleClick = () => {
const { value } = this.state;
setTimeout(() => this.showMessage(value), 3000);
};
可以解决,但点击时获取到当前的 user,再传递给 this.showMessage
,这样,即使 3 秒之后也是原来的值
缺点:每次都要从 this.props
中拿值,如果数据一多,写起来不符合人性
二、在构造函数中绑定方法
可以看线上 Demo
constructor(props) {
super(props);
this.showMessage = this.showMessage.bind(this);
this.handleClick = this.handleClick.bind(this);
}
这个方法解决不了问题。我们的问题是我们从 this.props
中读取数据太迟了—— 读取时已经不是我们所需要使用的上下文
三、利用闭包
把方法写进 render 中,这样每次渲染时就能捕获住当时所用的 props 或者 state
可以看线上 Demo
class ClassDemo extends React.Component {
state = {
value: ""
};
render() {
const { value } = this.state;
const showMessage = () => {
alert("最新值为 " + value);
};
const handleMessageChange = (e) => {
this.setState({ value: e.target.value });
};
const handleClick = () => {
setTimeout(showMessage, 3000);
};
return (
<div>
<input value={this.state.value} onChange={handleMessageChange} />
<button onClick={handleClick}>点击</button>
</div>
);
}
}
但是这个方法很蠢,这个写法和函数式组件有什么区别呢?还不如用函数式组件呢
函数式组件如果想保存最新的值呢
使用 useRef 保存最新的值,让组件获得最新的值
function MyComponent() {
const ref = useRef(null);
}
首先,ref 与实例都扮演同样的角色,ref 对象是一个有 current 属性的一个容器
上次的例子我们用函数式组件就可以这样写:
const FunctionDemo = () => {
const [value, setValue] = useState('');
const refValue = useRef('');
const showMessage = () => {
alert('最新值为 ' + refValue.current);
};
const handleMessageChange = (e) => {
setValue(e.target.value);
refValue.current = e.target.value;
};
const handleClick = () => {
setTimeout(showMessage, 3000);
};
return (
<div>
<input value={value} onChange={handleMessageChange} />
<button onClick={handleClick}>点击</button>
</div>
);
};
可以看线上 Demo
这里笔者提出两个疑问:
为什么 ref 能保存住最新的值?
为什么函数式组件会捕获,类组件不会呢?
后续文章会给出笔者的回答
后记
React team 曾在 2018 年 10月介绍 react hooks 时,描述了 class-based component 的诸多问题
- 许多逻辑处理都要使用生命周期方法,但它们各自只有一个,并且跟 class 声明强行绑定,难以找到有效的实现逻辑服用的途径
- 状态存储必须集中在一个 state 中进行管理,不易拆分
在 class-component 里,我们需要在同一个生命周期里做不同的事情,并且在不同的生命周期里协调同一件事情的不同阶段
在 function-component 里,我们则能将同一件事情的数据和行为封装到一个 custom hooks 里,使用时不需要再考虑协调问题,可以简单的获取数据或者触发行为
思考一下,react-hooks 模式如何使代码复用变得更简单,它跟 class-component 的差别是什么?
差别是,对象这个概念的瓦解——数据、行为及其关联式三个维度,它们不应被捆绑在对象中
数据可以单独声明(useState),行为也可以单独声明(useEffect),数据和行为可以进行可选的关联(custom-hooks)
数据可以单独组合,行为可以单独组合,组合的数据和组合的行为可以进行再度组合
组合的维度得到了横向和纵向的自由的扩展