前言
文档目录:
- 梦开始的地方
- 如何创造一个组件
- 属性 props
- 状态 state
- 生命周期 lifecycle
次文档为入门文档,暂时或以后不要用到的知识都给省略了
平时基本不用的尽量省略
优化相关尽量省略
React 17 相关尽量省略
一、梦开始的地方
react 文件一般用 .jsx 后缀来命名,但是也可以用 .js,两者从结果来说并无区别。
同理,如果是引入了 typescript,还可以用 .tsx 或者 .ts 来命名
特殊的文件名后缀只是为了方便 webpack 以及 ide 的识别,其并无特殊意义
入口文件(main)的编写
import React from "react"; // 每个 react 文件都必须引入 react 库,否则无法识别 react 语法
import ReactDOM from "react-dom";
// 声明(创建)一个简单的组件
function MyFirstComponent() {
return <div>hello world!</div>;
}
// 将组件渲染到实际 DOM 节点上
ReactDOM.render(
<MyFirstComponent></MyFirstComponent>,
document.getElementById("root")
);
二、如何创造一个组件
有且只有两种方式来构造一个组件
- 函数式(function component)
- 类定义(class component)
基础用法
下面分别用来两种方式来构造(渲染结果)一模一样的组件
import React from "react";
function MyFuncComp(props) {
console.log(props);
return <div>hello world!</div>;
}
class MyClassComp extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return <div>hello world!</div>;
}
}
演示两者的区别
从可以实现的效果来说,两者的主要区别:
- Function Component
- 没有生命周期钩子
- 无 state
- Class Component
- 有生命周期钩子
- 有 state
从 react 17 开始,Function Component 均可以通过不同的 Hook 来实现 Class Component 可以实现的功能
目前为止,你只需要了解最基本的编写方法即可,暂且用上面所表述的那样来区别两者
class MyClassComp extends React.Component {
constructor(props) {
super(props);
// 在构造函数中初始化 state
this.state = {
num: 0,
};
}
// 各种生命周期钩子
componentWillMount() {}
componentDidMount() {}
// shouldComponentUpdate(){}
// 声明响应点击的方法
handleAddClick = () => {
// Class Component 通过 setState 方法来改变 state
this.setState({
num: this.state.num + 1,
});
};
render() {
// 内部除了 props,还有 state
console.log(this.props, this.state);
return (
<div>
<p>点击次数:{this.state.num}</p>
<p onClick={this.handleAddClick}>点击我增加次数</p>
</div>
);
}
}
三、属性 props
父组件通过 props 传递属性(或方法)给子组件
function Child(props) {
return <div>父组件传递的props:{props.test}</div>;
}
function Parent() {
return <Child test="123"></Child>;
}
子节点 children
props
中有一个特殊的属性 children
用以存放父组件传递的子节点
并且 children
的值有三种可能:
-
undefined
当前组件没有子节点 -
object
当前组件有一个子节点 -
array
当前组件有多个子节点
关于 react 中的一些关于操作节点的 api,这里就不展开了
如
React.Children
、React.cloneElement
等等
import React from "react";
function Child(props) {
// const items=React.Children.only(props.children) // 只能返回一个children(对象),若children不止一个,则会抛出异常
// const items=props.children.map(item=>item); // 当children不止一个时,props.children为数组;只有一个时,为对象,所以props.children.map方法并不适用任何情况
// const items=React.Children.map(props.children,item=>item); // 适用与任一情况
const items = React.Children.toArray(props.children); // 等价于上面一行
return (
<div>
<p>父组件传递的一般属性:{props.test}</p>
<div>父组件子节点:{props.children}</div>
<div>父组件子节点:{items}</div>
</div>
);
}
function Parent() {
return (
<Child test="123">
<p>子节点1</p>
<p>子节点2</p>
</Child>
);
}
默认 props
通过 defaultProps
静态属性来定义该组件的默认 props
// Function Component
function MyCompOne(props) {
return <div>{props.myStr}</div>;
}
MyCompOne.defaultProps = {
myStr: "hello world",
};
// Class Component
class MyCompOne extends React.Component {
static defaultProps = {
myStr: "hello world",
};
render() {
return <div>{this.props.myStr}</div>;
}
}
其他
相关还有其他知识点,这里就不展开了
- props 的校验
- 方法传递时 this 指向问题
- 解构方式传递 props
四、状态 state
state 是一个组件的数据模型,是组件渲染时的数据依据,它与 props 共同来推动视图的渲染
改变 state
react 中通过 setState
来触发 state 的变化
有一下几点需要注意的:
- 不允许直接通过赋值语句来改变 state 的值
- 不是每次调用 setState 都会触发一次 render 的,state 所触发的更新是异步的
- 不要将“改变不需要触发重绘”的数据放在 state 中,这里会放到“性能优化”中详细介绍
class MyClassComp extends React.Component {
constructor(props) {
super(props);
// 在构造函数中初始化 state
this.state = {
num: 0,
};
}
// 声明响应点击的方法
handleAddClick = () => {
// Class Component 通过 setState 方法来改变 state
this.setState({
num: this.state.num + 1,
});
};
render() {
return (
<div>
<p>点击次数:{this.state.num}</p>
<p onClick={this.handleAddClick}>点击我增加次数</p>
</div>
);
}
}
五、生命周期 lifecycle
不同生命周期在组件的不同阶段被调用的顺序
这是 React 16 前的流程,后续也就只是针对 will 类型的钩子进行修改,目前开发过程中尽量不使用 will 之类的生命周期即可
<table border="1">
<thead>
<tr >
<th style="text-align: center;">Initialization</th>
<th style="text-align: center;">Mounting</th>
<th colspan="2" style="text-align: center;">Updation</th>
<th style="text-align: center;">Unmounting</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;">setup props and state</td>
<td>
<ol>
<li>componentWillMount</li>
<li>render</li>
<li>componentDidMount</li>
</ol>
</td>
<td>
<ol>
<li>componentWillReceiveProps</li>
<li>shouldComponentUpdate</li>
<li>componentWillUpdate</li>
<li>render</li>
<li>getSnapshotBeforeUpdate</li>
<li>componentDidUpdate</li>
</ol>
</td>
<td>
<ol>
<li>shouldComponentUpdate</li>
<li>componentWillUpdate</li>
<li>render</li>
<li>getSnapshotBeforeUpdate</li>
<li>componentDidUpdate</li>
</ol>
</td>
<td>componentWillUnmount</td>
</tr>
</tbody>
</table>
componentDidMount
组件挂载到 DOM 后调用,且只会被调用一次
一般就是在这里做一些诸如”发请求获取详情“等逻辑
function timeout(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
class ChildComp extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false,
name: "",
};
}
async componentDidMount() {
// 展示 loading 效果
this.setState({
loading: true,
});
await timeout(1000); // 用一个延时来模拟异步请求
this.setState({
loading: false,
name: "张大炮",
});
}
render() {
const { loading, name } = this.state;
if (loading) {
return <div>加载中...</div>;
} else {
return <div>我的名字:{name}</div>;
}
}
}
shouldComponentUpdate
当组件的更新机制被触发时,判断组件是否需要重新渲染(rerender)
触发组件的更新机制有以下几种场景:
- 父组件 rerender
- 自身 state 发生改变
当 state/props 发生变化时肯定会进入 shouldComponentUpdate,但是反之不是
譬如,当父组件导致子组件更新时,即使子组件接收的 props 是否发生改变,子组件都会触发更新机制
class ChildComp extends React.Component {
constructor(props) {
super(props);
this.state = {
num: 0,
};
}
handleAddClick = () => {
this.setState({
num: this.state.num + 1,
});
};
// shouldComponentUpdate(nextProps, nextState) {
// // 每次 props 或者 state 的改变都会进入本方法
// return true; // 方法需要传递一个 boolean 的返回值,true 表示“要渲染”
// }
shouldComponentUpdate(nextProps, nextState) {
// 判断前后的 props 或 state 是否发生了变化,来决定是否要重新渲染
return (
this.props.test !== nextProps.test || this.state.num !== nextState.num
);
}
render() {
return (
<div>
<p>父组件传递的属性:{this.props.test}</p>
<p>点击次数:{this.state.num}</p>
<p onClick={this.handleAddClick}>点击我增加次数</p>
</div>
);
}
}
shouldComponentUpdate 提供了组件渲染的优化方式,可以不写,其默认处理方式就是“始终返回 true”
还有一种内置
shouldComponentUpdate
的 Class Component,为React.PureComponent
,目前可以不需要了解,这里就不展开了
Class Component 还有一个 this.forceUpdate()
的内置方法,用于强制触发重新渲染(不用经过 shouldComponentUpdate),这里就不展开了,自行了解
componentDidUpdate
在组件重新渲染完成之后调用,并且首次渲染不会调用
componentDidUpdate(prevProps, prevState, snapshot){
if (this.props.userID !== prevProps.userID) {
this.fetchData(this.props.userID);
}
}
你可以在 componentDidUpdate 中调用 setState,但请注意,它必须像上面的例子那样被包装在一个条件中,否则有可能会导致无限循环。
不建议将父组件传递的 props 复制到子组件的 state 上,将 props 拷贝到 state 上会导致 bug
componentWillUnmout
在组件被销毁前会进入本生命周期
在这里一般需要进行以下操作:
- 清除掉你在本组件中使用的定时器 clearTimeout clearInterval
- 移除掉你在本组件中挂在的监听 removeEventListener
- 中止异步请求
class MyComp extends React.Component {
myInterval; // 要用一个属性来接收使用到的定时器
constructor(props) {
super(props);
this.state = {
second: 0,
size: "",
};
}
componentDidMount() {
// 绑定定时器
this.myInterval = window.setInterval(
() =>
this.setState({
second: this.state.second + 1,
}),
1000
);
// 绑定监听
window.addEventListener("resize", this.handleWindowResize);
// 发送异步请求
// TODO
}
componentWillUnmount() {
// 清除定时器
window.clearInterval(this.myInterval);
// 清除监听
window.removeEventListener("resize", this.handleWindowResize);
// 取消(未完成的)异步请求
// TODO
}
handleWindowResize = () => {
this.setState({
size: `${window.innerWidth}*${window.innerHeight}`,
});
};
render() {
return (
<div>
<p>倒计时:{this.state.second}</p>
<p>页面尺寸:{this.state.size}</p>
</div>
);
}
}
其他
static getDerivedStateFromProps
、getSnapshotBeforeUpdate
,应用面少,这里不展开了
在 react 17 中,以下几个生命周期将会被修改,并且必须加上 UNSAFE_
才行:
-
UNSAFE_componentWillMount
初始化逻辑应该在componentDidMount
上实现 -
UNSAFE_componentwillReceiveProps
原来功能建议在componentDidUpdate
上实现 -
UNSAFE_componentWillUpdate
这个实在想不出来能在什么地方用
这些 UNSAFE 的生命周期在后续还会被考虑直接移除,所以新的项目能不使用就不要使用了
至于为什么这些 componentWill 之类的东西要考虑被移除,可以去了解 React Fiber