生命周期
https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
组件的生命周期可分成三个状态:
- Mounting:已插入真实 DOM
- Updating:正在被重新渲染
- Unmounting:已移出真实 DOM
生命周期的方法有
- componentWillMount 在渲染前调用,在客户端也在服务端。
- componentDidMount : 在第一次渲染后调用,只在客户端。之后组件已经生成了对应的DOM结构,可以通过this.getDOMNode()来进行访问。 如果你想和其他JavaScript框架一起使用,可以在这个方法中调用setTimeout, setInterval或者发送AJAX请求等操作(防止异步操作阻塞UI)。
- componentWillReceiveProps 在组件接收到一个新的 prop (更新后)时被调用。这个方法在初始化render时不会被调用。
- shouldComponentUpdate 返回一个布尔值。在组件接收到新的props或者state时被调用。在初始化时或者使用forceUpdate时不被调用。可以在你确认不需要更新组件时使用。
- componentWillUpdate在组件接收到新的props或者state但还没有render时被调用。在初始化时不会被调用。
- componentDidUpdate 在组件完成更新后立即调用。在初始化时不会被调用。
- componentWillUnmount在组件从 DOM 中移除之前立刻被调用。
hooks
userState
useEffect
useContextuseContext可以帮助我们跨越组件层级直接传递变量,实现共享。 需要注意的是useContext和redux的作用是不同的!!!
useContext:解决的是组件之间值传递的问题
redux:是应用中统一管理状态的问题
但通过和useReducer的配合使用,可以实现类似Redux的作用。
useReducer
useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。(如果你熟悉 Redux 的话,就已经知道它如何工作了。)
在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数 。
useCallback
返回一个memoized回调函数,我的理解即返回一个函数的句柄,等同于函数的变量,因此你可以使用memoizedCallback()进行执行该函数或者传递给事件和子组件,这里可以推荐绝大多数事件或者子组件的方法使用useCallback,避免组件更新重复渲染。 因此useCallback中的doSomething并不会在定义时就执行,而是需要手动调用返回的memoizedCallback才是真的执行。简单理解为useCallback定义了一个函数,仅在deps发生变化时重新定义该函数,否则该函数的变量不会变化,事件和子组件内容也就不用重新绑定或者渲染。
useMemo
作为性能优化的手段
useRef
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。常见访问子组件
useImperativeMethods
useMutationEffect
useLayoutEffect
其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。
useEffect 和 useLayoutEffect 的区别?
useEffect 在渲染时是异步执行,并且要等到浏览器将所有变化渲染到屏幕后才会被执行。
useLayoutEffect 在渲染时是同步执行,其执行时机与 componentDidMount,componentDidUpdate 一致
对于 useEffect 和 useLayoutEffect 哪一个与 componentDidMount,componentDidUpdate 的是等价的?
useLayoutEffect,因为从源码中调用的位置来看,useLayoutEffect的 create 函数的调用位置、时机都和 componentDidMount,componentDidUpdate 一致,且都是被 React 同步调用,都会阻塞浏览器渲染。
useEffect 和 useLayoutEffect 哪一个与 componentWillUnmount 的是等价的?
同上,useLayoutEffect 的 detroy 函数的调用位置、时机与 componentWillUnmount 一致,且都是同步调用。useEffect 的 detroy 函数从调用时机上来看,更像是 componentDidUnmount (注意React 中并没有这个生命周期函数)。
为什么建议将修改 DOM 的操作里放到 useLayoutEffect 里,而不是 useEffect?
可以看到在流程9/10期间,DOM 已经被修改,但但浏览器渲染线程依旧处于被阻塞阶段,所以还没有发生回流、重绘过程。由于内存中的 DOM 已经被修改,通过 useLayoutEffect 可以拿到最新的 DOM 节点,并且在此时对 DOM 进行样式上的修改,假设修改了元素的 height,这些修改会在步骤 11 和 react 做出的更改一起被一次性渲染到屏幕上,依旧只有一次回流、重绘的代价。
如果放在 useEffect 里,useEffect 的函数会在组件渲染到屏幕之后执行,此时对 DOM 进行修改,会触发浏览器再次进行回流、重绘,增加了性能上的损耗。
函数式编程
JSX 本质 和 vdom
JSX 即 createElement 函数
执行生成 vnode
patch(elem, vnode) patch(vnode, newVnode)
合成事件
所有事件挂载到 document 上
event 不是原生的,是 SyntheticEvent 合成事件对象,模拟出来 DOM 事件所有能力
所有的事件,都被挂载到 document 上
和 Vue 事件不同,和 Dom 事件也不同
import React from "react";
class EventDemo extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "zhangsan",
list: [
{
id: "id-1",
title: "标题1",
},
{
id: "id-2",
title: "标题2",
},
{
id: "id-3",
title: "标题3",
},
],
};
// 修改方法的 this 指向
this.clickHandler1 = this.clickHandler1.bind(this);
}
render() {
// this - 使用 bind
// return <p onClick={this.clickHandler1}>{this.state.name}</p>;
// this - 使用静态方法
// return <p onClick={this.clickHandler2}>clickHandler2 {this.state.name}</p>;
// event
// return ( ,,,
// <a href="https://imooc.com/" onClick={this.clickHandler3}>
// click me
// </a>
// );
// 传递参数 - 用 bind(this, a, b)
return (
<ul>
{this.state.list.map((item, index) => {
return (
<li
key={item.id}
onClick={this.clickHandler4.bind(this, item.id, item.title)}
>
index {index}; title {item.title}
</li>
);
})}
</ul>
);
}
clickHandler1() {
// console.log('this....', this) // this 默认是 undefined
this.setState({
name: "lisi",
});
}
// 静态方法,this 指向当前实例
clickHandler2 = () => {
this.setState({
name: "lisi",
});
};
// 获取 event
clickHandler3 = (event) => {
event.preventDefault(); // 阻止默认行为
event.stopPropagation(); // 阻止冒泡
console.log("target", event.target); // 指向当前元素,即当前元素触发
console.log("current target", event.currentTarget); // 指向当前元素,假象!!!
// 注意,event 其实是 React 封装的。可以看 __proto__.constructor 是 SyntheticEvent 组合事件
console.log("event", event); // 不是原生的 Event ,原生的 MouseEvent
console.log("event.__proto__.constructor", event.__proto__.constructor);
// 原生 event 如下。其 __proto__.constructor 是 MouseEvent
console.log("nativeEvent", event.nativeEvent);
console.log("nativeEvent target", event.nativeEvent.target); // 指向当前元素,即当前元素触发
console.log("nativeEvent current target", event.nativeEvent.currentTarget); // 指向 document !!!
// 1. event 是 SyntheticEvent ,模拟出来 DOM 事件所有能力
// 2. event.nativeEvent 是原生事件对象
// 3. 所有的事件,都被挂载到 document 上
// 4. 和 DOM 事件不一样,和 Vue 事件也不一样
};
// 传递参数
clickHandler4(id, title, event) {
console.log(id, title);
console.log("event", event); // 最后追加一个参数,即可接收 event
}
}
export default EventDemo;
为何要合成事件机制
更好的兼容性和跨平台
载到 document ,减少内存消化,避免频繁解绑
方便事件的统一管理(如事务机制)
setState 和 batchUpdate
有时异步(普通使用),有时同步(setTimeout、DOM事件)
有时合并(对象形式),有时不合并(函数形式)
后者比较好理解(像Object.assign),主要讲解前者
import React from "react";
// 函数组件(后面会讲),默认没有 state
class StateDemo extends React.Component {
constructor(props) {
super(props);
// 第一,state 要在构造函数中定义
this.state = {
count: 0,
};
}
render() {
return (
<div>
<p>{this.state.count}</p>
<button onClick={this.increase}>累加</button>
</div>
);
}
increase = () => {
// 第二,不要直接修改 state ,使用不可变值 ----------------------------
// this.state.count++ // 错误
// this.setState({
// count: this.state.count + 1, // SCU
// });
// 操作数组、对象的的常用形式
// 第三,setState 可能是异步更新(有可能是同步更新) ----------------------------
// this.setState(
// {
// count: this.state.count + 1,
// },
// () => {
// // 联想 Vue $nextTick - DOM
// console.log("count by callback", this.state.count); // 回调函数中可以拿到最新的 state
// }
// );
// console.log("count", this.state.count); // 异步的,拿不到最新值
// // setTimeout 中 setState 是同步的
// setTimeout(() => {
// this.setState({
// count: this.state.count + 1,
// });
// console.log("count in setTimeout", this.state.count);
// }, 0);
// 自己定义的 DOM 事件,setState 是同步的。再 componentDidMount 中
// 第四,state 异步更新的话,更新前会被合并 ----------------------------
// 传入对象,会被合并(类似 Object.assign )。执行结果只一次 +1
// this.setState({
// count: this.state.count + 1,
// });
// this.setState({
// count: this.state.count + 1,
// });
// this.setState({
// count: this.state.count + 1,
// });
// 传入函数,不会被合并。执行结果是 +3
this.setState((prevState, props) => {
return {
count: prevState.count + 1,
};
});
this.setState((prevState, props) => {
return {
count: prevState.count + 1,
};
});
this.setState((prevState, props) => {
return {
count: prevState.count + 1,
};
});
};
// bodyClickHandler = () => {
// this.setState({
// count: this.state.count + 1,
// });
// console.log("count in body event", this.state.count);
// };
// componentDidMount() {
// // 自己定义的 DOM 事件,setState 是同步的
// document.body.addEventListener("click", this.bodyClickHandler);
// }
// componentWillUnmount() {
// // 及时销毁自定义 DOM 事件
// document.body.removeEventListener("click", this.bodyClickHandler);
// // clearTimeout
// }
}
export default StateDemo;
// -------------------------- 我是分割线 -----------------------------
// // 不可变值(函数式编程,纯函数) - 数组
// const list5Copy = this.state.list5.slice();
// list5Copy.splice(2, 0, "a"); // 中间插入/删除
// this.setState({
// list1: this.state.list1.concat(100), // 追加
// list2: [...this.state.list2, 100], // 追加
// list3: this.state.list3.slice(0, 3), // 截取
// list4: this.state.list4.filter((item) => item > 100), // 筛选
// list5: list5Copy, // 其他操作
// });
// // 注意,不能直接对 this.state.list 进行 push pop splice 等,这样违反不可变值
// // 不可变值 - 对象
// this.setState({
// obj1: Object.assign({}, this.state.obj1, { a: 100 }),
// obj2: { ...this.state.obj2, a: 100 },
// });
// // 注意,不能直接对 this.state.obj 进行属性设置,这样违反不可变值
this.setState异步还是同步?
setState 无所谓异步还是同步
看是否能命中 batchedUpdates 机制,
Legacy模式命中异步,反之同步;Concurrent模式统一异步
判断 isBatchingUpdates
例:
- 合成事件中是异步
- 钩子函数中的是异步
- 原生事件中是同步
- setTimeout中是同步
哪些能命中 batchUpdate 机制
生命周期(和它调用的函数)
React 中注册的事件(和它调用的函数)
React 可以 “管理”的入口哪些不能命中 batchUpdate 机制
setTimeout setInterval等(和它调用的函数)
自定义的 DOM 事件(和它调用的函数)
React “管不到”的入口transaction 事务机制
组件渲染和更新过程
JSX 如何渲染成页面
setState 之后如何更新页面
全流程
更新的两个阶段:reconciliation commit
React fiber
表单校验
受控组件(非受控组件)
input textarea select 用 value
checkbox radio 用 checked
import React, { memo } from "react";
import { Form, Input } from "antd";
const Test = memo((props) => {
const [form] = Form.useForm();
const passwordValidator = (rule, value, callback) => {
const { getFieldValue } = form;
if (value && value !== getFieldValue("password")) {
callback("两次输入不一致!");
}
callback();
};
return (
<Form from={form}>
<Form.Item label="非空限制">
{getFieldDecorator("name", {
rules: [
{
required: true,
message: "不能为空",
},
],
})(<Input placeholder="请输入名称" />)}
</Form.Item>
<Form.Item label="字符串限制-范围限制">
{getFieldDecorator("password", {
rules: [
{
required: true,
message: "密码不能为空",
},
{
min: 4,
message: "密码不能少于4个字符",
},
{
max: 6,
message: "密码不能大于6个字符",
},
],
})(<Input placeholder="请输入密码" type="password" />)}
</Form.Item>
<Form.Item label="字符串限制-长度限制">
{getFieldDecorator("nickname", {
rules: [
{
required: true,
message: "昵称不能为空",
},
{
len: 4,
message: "长度需4个字符",
},
],
})(<Input placeholder="请输入昵称" />)}
</Form.Item>
<Form.Item label="自定义校验">
{getFieldDecorator("passwordcomfire", {
rules: [
{
required: true,
message: "请再次输入密码",
},
{
validator: passwordValidator,
},
],
})(<Input placeholder="请输入密码" type="password" />)}
</Form.Item>
<Form.Item label="空格校验">
{getFieldDecorator("hobody", {
rules: [
{
whitespace: true,
message: "不能输入空格",
},
],
})(<Input placeholder="请输入昵称" />)}
</Form.Item>
<Form.Item label="正则校验">
{getFieldDecorator("qbc", {
rules: [
{
message: "只能输入数字",
pattern: /^[0-9]+$/,
},
],
})(<Input placeholder="请输入ABC" />)}
</Form.Item>
</Form>
);
});
export default Test;
组件使用
props 传递数据
props 传递函数
props 类型检查
异步加载
import ReactDOM from "react-dom";
import React, { Component, lazy, Suspense } from "react";
const Sub = lazy(() => import("./sub"));
class App extends Component {
render() {
return (
<div>
<Suspense fallback={<div>loading</div>}>
<Sub />
</Suspense>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
组件优化-经典面试题
import React, { Component, PureComponent, memo } from "react";
import ReactDOM from "react-dom";
// 组件的优化
class Sub extends PureComponent {
// shouldComponentUpdate(nextProps, nextState) {
// if (nextProps.name === this.props.name) {
// return false;
// }
// return true;
// }
render() {
console.log("sub render");
return <div>sub</div>;
}
}
const SubFun = memo((props) => {
console.log("SubFun render");
return <div>SubFun</div>;
});
class App extends Component {
state = {
count: 0,
};
handleClick = () => {
this.setState({
count: this.state.count + 1,
});
};
callback = () => {};
render() {
console.log("render");
const { count } = this.state;
return (
<div>
{/* <Sub name="wcd" /> */}
{/* <Sub cb={() => this.callback()} /> */}
<Sub cb={this.callback} />
<SubFun cb={this.callback} />
<p>{count}</p>
<button onClick={this.handleClick}>button</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));