一、class组件
- 基本写法:
import React from "react";
import ReactDOM from "react-dom";
interface Props {
message: string;
}
interface State {
n: number;
}
class App extends React.Component<Props, State> {
constructor(props) {
super(props);
this.state = {n: 1};
}
onClickN = () => {
this.setState({n: 2});
};
render() {
return(
<div>
n:{this.state.n}
<button onClick={this.onClickN}>+1</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
- 关键词:
extends React.Component
、constructor(props)
、super(props)
、render() { return()}
、、
二、函数组件
- 基本写法:
import React, { useState } from "react";
import ReactDOM from "react-dom";
interface Props {
message?: any;
}
const App: React.FunctionComponent<Props> = ({
message = "default message"
}) => {
const [n, setN] = useState(0);
const onClickN = () => {
setN(n + 1);
};
return (
<div>
<h1>{message}</h1>
n:{n}
<button onClick={onClickN}>n+1</button>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
三、对比函数组件和类组件中单组件状态管理
1. 函数组件:
const App = () => {
const [obj, setObj] = useState({a: 1, b: 2})
const onClick = () => {
/*** 错误写法 ***/
obj.a = obj.a + 1
setObj(obj) //不会更新UI
/*** ***/
/*** 正确写法 ***/
setObj({
...obj,
a: obj.a+1,
})
/*** ***/
}
return (
<div>
a:{obj.a}
<button onClick={onClick}>a+1</button>
</div>
);
}
只有setObj()里面的引用变了,才会重新渲染,setObj里面需要创建新的对象。
但是,如果state数据相对复杂,改起来会比较麻烦,比如:
newState = {...state,
nation: {...state.nation,
province:{...state.user.province,
city: 'beijing'
}
}
}
2. class组件:
只要this.setN(this.n)
,不管setN()里面的值是否改变,都会重新渲染(只要setState,就会触发render,为了避免多余的,有shouldComponentUpdate:
- shouldComponentUpdate:
shouldComponentUpdate(newProps, newState) {
for (let key in newState) {
if (newState[key] !== this.state[key]) {
return true; // render
}
}
for (let key in newProps) {
if (newProps[key] !== this.props[key]) {
return true; // render
}
}
console.log("此处阻止 render");
return false; // 阻止 render
}
-
或者:PureComponent:
class App extends React.PureComponent{}
代替以上代码。
(但是,state如果是对象,而修改了对象里面的值,而不是生成新的对象,newState[key]
和this.state[key]
就永远都相等,代码就失效了)
综上,不管是函数组件还是Class组件,尽量都不要去修改原state,而是生成新的state,而遇到相对复杂的state对象会很麻烦,推荐immer.js,这个库让创建新对象看起来像是在修改原对象。
3. immer.js
const newData = produce(data, (draft) => {
draft.user.group.name = "group 2";
});
- 代码中的 draft 实际上是个 proxy,会把你的修改收集起来,然后创建新对象
- immer 创建新对象不是直接深拷贝,深拷贝很浪费内存
- immer.js 永远都不会去修改原数据
- 在新的对象里复制原来不变的数据,而修改要变的数据
选择class组件还是函数组件?
最优原则:每次创建新的state,必要时候使用shuldComponentUpadte/useMemo(useCallback)来减少不必要的render。其实,多余的 render 可能浪费不了多少性能。相反,为了减少render而写的代码如果太复杂,可能比render更消耗性能。