简介
本篇我们只要介绍react中类组件与函数组件两种组件的写法、两者的优缺点;
同时对在我们的项目开发中该使用类组件还是函数组件进行思考分析;
废话不多说进入正题~
类组件
设计思路
类组件时面向对象编程的思想,在其中我们去设计类组件时使用state
对象去完成组件内部数据的定义,结合react的生命周期构造(componentDidMount、componentDidUpdate、componentWillUnmount等等)完成整个组件的开发设计;
具体实现
示例
import React, {Component} from 'react';
class MyDemo extends Component {
defaultProps = {
test: 1, // 如果没有给组件传test,那么props中test默认为1
}
constructor(props) {
super(props);
this.state = {
name: '类组件',
info: 'demo',
}
}
componentDidMount () {
console.log('我是一个生命周期函数,在完成首次渲染后调用');
this.sayHello();
}
componentDidUpdate() {
console.log('我是一个生命周期函数,在完成数据更新并渲染完成后调用')
}
componentWillUnmount() {
console.log('我是一个生命周期函数,在组件销毁前调用');
}
sayHello () {
console.log('hello!我是', this.state);
}
changeInfoValue () {
this.setState({
info: 'newDemo',
},() => {
console.log('state中的info属性值完成更新,变为', this.state.info);
});
}
render() {
return (
<div onClick={changeInfoValue}>
{this.state.name}
</div>
)
}
}
export default MyDemo;
正如上面所示,便完成了我们一个类组件,当然其中还有一些其他的生命周期函数,我们在上一篇已经讲过了,不了解的小伙伴回到上一篇去看看,这里就不再介绍;
函数组件
设计模式
函数组件中没有 this,它提供了hook一系列方法来代替原先 类函数 中的一些特性,例如用useState
创建状态变量,使用useEffect实现类似于componmentDidMount、shouldComponentUpdate等生命周期钩子函数的功能。
为啥都有类组件的模式了,还要推出函数组件呢
核心目的:为了降低复杂度、降低学习成本和理解成本;(这里并不是说类组件就不好啊~)
- class也是有学习成本的,你必须去理解class中this的指向,其没有稳定的法语提案,但是对比而言函数就简单粗暴些了;
- 要关注很多生命周期的状态这些,组件是一个套着一个,每个小组件中又有各个自己的生命周期,这就让一个复杂的组件有时候变得难以理解(当然这也和开发者自身的组件设计有关),函数组件则没有这些,hook提供的的方法在我看来对一下数据的状态管理更加可预测;
ps:react 并计划说要把类组件从react中移除,在一个项目中类组件和函数组件是可以共存的,facebokk就有一堆的类组件,但是他们并没有把他们全部重写成函数组件,在后续代码中他们会同时去使用 hook 和 class
什么是Hook
Hook是 react 在16.8后新加的特性,它能让我们不通过 class类的方式去写一个组件,也就是用函数的方式去写,它提供一系列的hook方法去代替state、生命周期等react特性;
ps:hook方法不能在类组件中去使用,一定写在函数组件的最外层,注意是最外层!!!
函数组件中有哪些Hook呢
useState
import { useState } from 'react';
const [state, setState] = useState(initialState);
返回一个 state,以及更新 state 的函数。
在初始渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同。
setState 函数用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列。
注意:如果你的更新值与当前 state 完全相同(React 使用 Object.is比较算法 来比较 state),则随后的重渲染会被完全跳过。
useEffect
import { useEffect } from 'react';
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
// 清除订阅
subscription.unsubscribe();
};
}, []);
在了解useEffect
时你要知道几个概念:如何清除effect、effect的执行时机、如何控制effect的执行条件
- 清除effect:在传入函数中的 return中加入一个函数,则这个函数就会在当组件卸载前执行;
- effect的执行时机:执行时机与 componentDidMount、componentDidUpdate 不同的是,传给 useEffect 的函数会在浏览器完成布局与绘制之后,在一个延迟事件中被调用。
- 增加effect的执行条件:可以给 useEffect 传递第二个参数,它是 effect 所依赖的值数组
注意
如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。这并不属于特殊情况 —— 它依然遵循输入数组的工作方式。
useContext
之前我们有讲过父子组件间传值的一种方式
先通过React.createContext()
方式创建一个context,然后在函数内通过useContext()
的方式返回context的当前值
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
const ThemeContext = React.createContext(themes.light);
function App() {
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}
useReducer
当一个state有多个可以改变其的方法,或者说依赖了很多的其他state时,用useState
显然通过setState
去改state时需要用到很多的方法,写法繁杂,此时就可以用useReducer
去代替它
useReducer
时useState
的平替方法
示例
const demo = () => {
const initState = {count: 0};
const reduce = (state, action) => {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
case 'reset':
return init(action.payload);
default:
throw new Error();
}
const [state, dispatch] = useReducer(reduce, initState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'reset', payload: initState})}>
Reset
</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
ps:一般创建用到它比较少,但是如果我们用上它结合useContext就可以模拟实现redux的行为
useMemo
useMemo
是用来缓存计算属性的;
计算属性其实是函数的返回值,或者说指那些以返回一个值为目标的函数。
有些函数,需要我们手动的去点击,去完成一些动作才触发。而有些函数,则是直接在渲染的时候就执行,在DOM区域被当作属性值一样去使用,这些就被称为计算属性。
有时候我们直接在dom中加入计算属性,那么每次渲染时它都会执行得到一个值,但是这个值明明每次执行得结果都一样,没必要每次都执行呀,这时候我们就可以用上useMemo
,和vue中computer中定义方法差不多
const demo = () => {
const [count, setCount] = useState(0);
const doubleCount = useMemo(() => {
return count*2;
},[count])
const newCount = useMemo(() => {
let currentCount = 0
for(let i=0;i<100000;++i){
currentCount = currentCount + i + count;
}
return currentCount;
},[count])
return (
<div>
{doubleCount}
{newCount}
</div>
)
}
ps:useMemo
不是越多越好哈,缓存也是需要成本的,那我们时候需要优化呢?但计算量很大很复杂时用更合适,不要滥用,像上面例子中doubleCount
没必要了用useMemo
,newCount
就可以用useMemo
;
useCallback
它和useMemo
的功能都做缓存用,只不过useCallback
缓存的时函数,useMemo
缓存的计算属性也就是值
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
ps:一样不能滥用,使用场景很少吧(个人理解)
useRef
一般我们在react组件中获取Dom有两个方法,一个是用传统的document,另外一个就是用useRef
方法
const demoDom = () => {
const input = useRef(null);
const inputEl = useRef(null);
const onButtonClick = () => {
//这里的inputEl.current和document.getElementById('myInput')一样
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} id="myInput" type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
除了上面的hook,react还提供了useImperativeHandle
、useLayoutEffect
、useDebugValue
这些hook方法,这里就不展开说了,可以去react官网看看
通过react提供的上面这些hook方法,我们就能模拟类函数中的生命周期函数,用函数的设计模式去写组件
总结
类组件和函数组件共同点
- 无论是使用函数组件还是类组件,它都不能修改自己的 props 属性。
- React是单向数据流,父组件改变了属性,那么子组件视图会更新。
- 函数组件和类组件都可以接收 props 属性并且返回React元素。
- 属性 props 是外界传递过来的,状态 state 是组件本身的,状态可以在组件中任意修改。
类组件和函数组件区别
当然函数组件和类组件是有区别的,而且函数组件比类组件性能好,因为类组件使用的时候要实例化,而函数组件直接执行函数取返回结果即可。为了提高性能,尽量使用函数组件(多写无状态组件,少写有状态组件)。
区别 |
类组件 |
函数组件 |
是否有this |
有 |
没有 |
是否有生命周期 |
有 |
没有 |
是否有状态state |
有 |
没有 |