友情提示:
- 代码演示默认是 React V17 版本 ReactDOM.render 模式,特殊的会说明。
- 以下代码 render 方法不是应该只执行一次,为什么 debugger 时,render 执行多次?
class App extends React.Component {
render(){
debugger;
console.log('render');
return <div>ddd</div>
}
}
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
官网严格模式给出答案,仅开发模式开启。
好处:识别不安全的生命周期;关于使用过时字符串 ref API 的警告;关于使用废弃的 findDOMNode 方法的警告;检测意外的副作用;检测过时的 context API 等;
JSX
JSX 是 JavaScript 语法扩展,v16 版本经过 bable 编译后就是 React.createElement() 调用。
例如:const ele = <div>hello</div>; 经过编译后转为
var ele = React.createElement("div", null, "hello");
注意:react 17 新的转换, 是工具自动从 React 的 package 中引入新的入口函数并调用
(import {jsx as _jsx} from 'react/jsx-runtime';)。
JSX 语法
- 用
{}包裹变量、表达式、函数等; HTML类型标签小写,组件类型标签首字母要大写;- 所有标签要闭合;
- 属性用小驼峰;
使用例子:
import React from 'react';
function Hello() {
return <div>hello func component</div>;
}
class App extends React.Component {
flag = false;
sayAge = () => <div>{1 + 3}</div>;
showApp = () => {
const eleJSX = (
<div className="test">
<div style={{ backgroundColor: 'red', fontSize: '40px' }}>1</div>
<>
<div>2 fragment</div>
</>
3 文本
<Hello />
{this.sayAge()}
{this.flag ? 'flag is true' : <div>flag is false</div>}
</div>
);
console.log(eleJSX);
return eleJSX;
};
render() {
return this.showApp();
}
}
export default App;
JSX 好处
- 编写方便,直观,提升开发效率;
- 抽象 React 元素的创建,使得编写组件简单;
- 防止注入攻击;
React 元素
描述屏幕上看到的内容,是构成 React 应用最小单元,实际上就是一个 JS 对象。
前面代码返回的 ReactElement 格式如下:
创建方式
-
JSX 语法
-
React.createElement(type, config, children)
-
React.cloneElement(element, config, children)
元素渲染
要想 React 元素呈现到浏览器中,我们需要一个容器,一般是 index.html 中有<div id="root"></div> ,通过ReactDOM.render 方法挂到容器里,ReactDOM.render() 通常只会调用一次,初始化时,将元素挂到容器内,更新时,React DOM 会将元素和它的子元素与它们之前的状态进行比较,并只会更新需要更新的部分。
组件 UI = f(data)
组件分为类组件和函数组件:
类组件内部通过constructClassInstance 方法内执行 new 调用,
class App extends React.Component {
constructor() {
super();
this.state = {
num: 0
};
}
changeNum = () => {
this.setState({
num: Math.random()
});
};
render() {
return (
<div>
<Hello num={this.state.num} />
<button onClick={this.changeNum}>click</button>
</div>
);
}
}
函数组件内部通过 renderWithHooks 方法内 函数执行
function Hello(props) {
return <div>hello func component{props.num}</div>;
}
事件
- react 事件和原生事件区别(形态上看 驼峰 函数 不加括号 )
在 React 中不能使用 return false 的方式阻止事件的默认行为,必须要显式的调用事件对象的 preventDefault 方法来阻止事件的默认行为。
- 处理方式和传参
1、箭头函数 每次 render ,重新生成处理函数
<button onClick={(event) => {this.handleClick(event, 'dxx')}}>click me</button>
2、render 中 bind 绑定,每次 render ,重新生成处理函数
<button onClick={this.handleClick.bind(this, 'dxx')}>click me</button>
3、在构造函数中 bind 绑定,多个事件比较繁琐
class App extends React.Component {
constructor () {
this.handleClick = this.ha ndleClick.bind(this, 'dxx');
}
handleClick = function () {}
render () {
return (
<button onClick={this.handleClick}>click me</button>
)
}
}
4、属性是箭头函数, 不需要收到绑定 this ,没有函数重新创建问题
class App extends React.Component {
handleClick = (event) => {}
render () {
return (
<button onClick={this.handleClick}>click me</button>
)
}
}
| 事件写法 |
|---|
| onClick ={ this.method } / method = () => {} |
| onClick ={ this.method } / constructor 中 this.method= this.method.bind(this) |
| onClick = { this.method.bind(this) } |
| onClick = { () => { this.method() } } / /首先 onClick 值为用大括号包裹一个函数(这个函数由里面箭头函数执行返回一个函数), |
通信
父组件向子组件
通过 props 传递
function Child(props){
return <div>{props.title}</div>
}
class App extends React.Component {
render(){
return <Child title="dxx"/>
}
}
子组件向父组件
父组件中 props 传递回调函数给子组件,子组件调用回调函数,将参数传给父组件
function Child(props) {
const { title, setName } = props;
return (
<div>
<p>{title}</p>
<button onClick={() => setName('child')}>click</button>
</div>
);
}
class App extends React.Component {
setName = p => {
console.log(p);// child
};
render() {
return <Child title="dxx" setName={this.setName} />;
}
}
跨层级组件
通过 context 共享上下文通信
| 版本 | 提供者 | 消费者 |
|---|---|---|
| v16.3 之前 | getChildContext 对象、 static childContextTypes对象结构 | this.context拿到、static contextTypes结构 |
| v16.3 之后 | context.Provider(context: createContext 创建) 中的 vaule 属性 | 1、context.Consumer (render props 方式); 2、 this.context、static contextType获取;3、函数组件通过 useContext`; |
const Theme = {
theme1: {
color: 'red',
background: 'green'
},
theme2: {
color: 'white',
background: 'black'
}
};
const ThemeContext = createContext(Theme);
function Child(props) {
const context = useContext(ThemeContext);
return <div style={{ color: context.color, backgroundColor: context.background }}>child</div>;
}
class Son extends React.Component {
constructor(props, context) {
super(props);
}
// render() {
// return (
// <ThemeContext.Consumer>
// {context => {
// return (
// <>
// <div style={{ color: context.color, backgroundColor: context.background }}>son</div>
// </>
// );
// }}
// </ThemeContext.Consumer>
// );
// }
static contextType = ThemeContext;
render() {
return <div style={{ color: this.context.color, backgroundColor: this.context.background }}>son</div>;
}
}
class Fathter extends React.Component {
render() {
return (
<div>
<Child />
<Son />
</div>
);
}
}
class App extends React.Component {
render() {
return (
<ThemeContext.Provider value={Theme.theme2}>
<Fathter />
</ThemeContext.Provider>
);
}
}
相邻组件
状态提升,在它们父组件中管理
const defaultState = {
value: 0
};
function reducer(state, action) {
switch (action.type) {
case 'ADD':
return { ...state, value: state.value + 1 };
case 'REDUCE':
return { ...state, value: state.value - 1 };
default:
throw new Error();
}
}
const Context = createContext(null);
function FirstChild() {
const AppContext = useContext(Context);
return (
<div>
<button
onClick={() => {
AppContext.dispatch({ type: 'ADD' });
}}
>
ADD
</button>
<button
onClick={() => {
AppContext.dispatch({ type: 'REDUCE' });
}}
>
REDUCE
</button>
</div>
);
}
function SecondChild() {
const AppContext = useContext(Context);
return <div>{AppContext.state.value}</div>;
}
function App() {
const [state, dispatch] = useReducer(reducer, defaultState);
return (
<Context.Provider value={{ state, dispatch }}>
<FirstChild />
<SecondChild />
</Context.Provider>
);
}
发布订阅模式
class EventEmmiter {
es = {};
on(eventName, cb, once = false) {
if (!this.es[eventName]) {
this.es[eventName] = [];
}
this.es[eventName].push({ cb, once });
}
emit(eventName, ...args) {
const listerns = this.es[eventName] || [];
for (let i = 0; i < listerns.length; i++) {
const { cb, once } = listerns[i];
cb.apply(this, args);
if (once) {
listerns.splice(i--, 1);
}
}
}
off(eventName, cb) {
if (eventName === undefined) {
this.es = {};
} else {
if (cb === undefined) {
delete this.es[eventName];
} else {
const listerns = this.es[eventName] || [];
for (let i = 0; i < listerns.length; i++) {
if (listerns[i].cb === cb) {
listerns.splice(i--, 1);
}
}
}
}
}
once(eventName, cb) {
this.on(eventName, cb, true);
}
}
框架
组件的生命周期
React 15 生命周期
图片来源网上
首次渲染,执行 Mounting 列的函数;
更新渲染,分 props 触发,和 state 触发,props 多一个(componentWillReceiveProps)执行,
父组件导致组件重新渲染,即使 props 没改变,也会调用 componentWillReceiveProps;
卸载,执行 componentWillUnmount,
两种情况触发:1、组件从父组件移除;2、组件设置了 key,父组件在 render 过程中发现 key 和上次不一致;
React 16 生命周期
v16 相比 v15
去掉 v15 的 componentWillMount、 componentWillReceiveProps 、componentWillUpdate;
增加 getDerivedStateFromProps、getSnapshotBeforeUpdate;
v16.4 让 getDerivedStateFromProps 在 render 前都会执行;
React 生命周期去掉的函数都是 render 阶段的,升级生命周期函数是为了更好适配 Fiber 架构
render() 把渲染的内容返回出来,不会操作真实的 DOM
v16 render() 返回做了改进,允许返回数组和字符串(之前必须返回单个元素)
每个生命周期处理什么
| 生命周期函数 | 处理 |
|---|---|
| constructor | 执行一次,初始化工作(state、事件等) |
| static getDerivedStateFromProps(nextProps, prevState) | 代替 componentWillReceiveProps;将 props 映射 state;返回值合并到 state 中,作为 shouldComponentUpdate 第二个参数 newState,用来判断是否渲染; |
| getSnapshotBeforeUpdate(prevProps, preState) | 记录更新前的信息,传递给 componentDidUpdate 第三个参数 |
| componentDidUpdate(prevProps, preState, snapshot) | DOM 已更新,获取更新后的信息 |
| componentDidMount | DOM 操作;数据服务请求; |
| shouldComponentUpdate(newProps,newState,newContext) | 性能优化 |
| componentWillUnMount | 清除,销毁工作 |
| componentDidCatch | render() 出错时回退页面 |
setState
React 视图的改变来源于 state 的改变,类组件通过 setState 更新组件。
用法:
setState(partialState, callback)
partialState 可以为 null、对象、函数;callback 回调中可以拿到更新后的 state 值;
import React from 'react';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
num: 0
};
}
changeNum = () => {
// this.setState(null);
this.setState(
{
num: this.state.num + 1
},
function () {
console.log(this.state.num);
}
);
this.setState({
num: this.state.num + 1
});
this.setState((prevState, props) => {
return {
num: prevState.num + 1
};
});
};
render() {
return (
<div>
<input type="text" value={this.state.num} readOnly />
<button onClick={this.changeNum}>change</button>
</div>
);
}
}
初始时页面显示 0 ,一次点击后变为 2。
到底是怎么更新的?
一次 setState 更新流程:
v16.13.1 版本
每次执行 setState ,内部执行 enqueueSetState ,创建一个 update ,把它放入当前 Fiber 更新队列中,执行 scheduleUpdateOnFiber调度更新。
那么一次触发多个 setState 是不是 render 多次呢?答案是否定的。React 会对多次 setState 进行批量更新。
React 事件是合成事件,所有事件都会经过 dispatchEventForLegacyPluginEventSystem 函数,执行 batchedEventUpdates,通过
isBatchingEventUpdates 开关,控制批量更新。
代码流程:
一次点击事件 -> isBatchingEventUpdates 为 true -> changeNum,三次 setState 合并 -> render 执行 -> 更新 DOM -> callback 执行-> isBatchingEventUpdates 为 false
打破批量更新,同步执行
- 如下改为,将 setState 包裹在 setTimeout 中
changeNum = () => {
setTimeout(() => {
this.setState(
{
num: this.state.num + 1
},
function () {
console.log(this.state.num);
}
);
this.setState({
num: this.state.num + 1
});
this.setState((prevState, props) => {
return {
num: prevState.num + 1
};
});
});
};
执行流程:
由于同步执行,页面显示最终效果是点击按钮,每次 +3 ,实际执行了 3 次 render
- 绑定原生事件
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
num: 0
};
}
handleNative = () => {
this.setState({num: this.state.num + 1});
this.setState({num: this.state.num + 1});
this.setState({num: this.state.num + 1});
}
componentDidMount() {
document.getElementById('native').addEventListener('click', this.handleNative)
}
render() {
return (
<div>
<input type="text" value={this.state.num} readOnly />
<button id="native">change</button>
</div>
);
}
}
每点一次按钮,render 执行 3 次 ,页面显示结果 +3
ReactDOM.flushSync
以下代码点击后页面展示多少?
import React from 'react';
import ReactDOM from 'react-dom';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
num: 0
};
}
changeNum = () => {
this.setState({
num: this.state.num + 1
});
this.setState({
num: this.state.num + 1
});
this.setState((prevState, props) => {
return {
num: prevState.num + 1
};
});
console.log('flushSync 前', this.state.num);
// 触发 render 前面的 setState 会合并
ReactDOM.flushSync(() => {
this.setState((prevState, props) => {
return {
num: prevState.num + 1
};
});
});
console.log('flushSync 后', this.state.num);
};
render() {
return (
<div>
<input type="text" value={this.state.num} readOnly />
<button onClick={this.changeNum}>change</button>
</div>
);
}
}
答案页面显示 3,控制台:flushSync 前 0,flushSync 后 3
flushSync setState 合并 -> render -> 更新
扩展一:flushSync 后加 setState,其他不变
changeNum = () => {
this.setState({
num: this.state.num + 1
});
this.setState({
num: this.state.num + 1
});
this.setState((prevState, props) => {
return {
num: prevState.num + 1
};
});
console.log('flushSync 前', this.state.num);
// 触发 render 前面的 setState 会合并
ReactDOM.flushSync(() => {
this.setState((prevState, props) => {
return {
num: prevState.num + 1
};
});
});
console.log('flushSync 后', this.state.num);
this.setState({
num: this.state.num + 1
});
console.log('flushSync setState 后', this.state.num);
};
答案页面显示 4,控制台:flushSync 前 0,flushSync 后 3,flushSync setState 后 3
flushSync setState 合并 -> render -> 更新 -> setState -> render -> 更新
扩展二:加 setTimeout
changeNum = () => {
this.setState({
num: this.state.num + 1
});
this.setState({
num: this.state.num + 1
});
this.setState((prevState, props) => {
return {
num: prevState.num + 1
};
});
console.log('flushSync 前', this.state.num);
// 触发 render 前面的 setState 会合并
ReactDOM.flushSync(() => {
this.setState((prevState, props) => {
return {
num: prevState.num + 1
};
});
});
console.log('flushSync 后', this.state.num);
this.setState({
num: this.state.num + 1
});
this.setState({
num: this.state.num + 1
});
console.log('flushSync setState 后', this.state.num);
setTimeout(() => {
this.setState({
num: this.state.num + 1
});
console.log('setTimeout 内', this.state.num);
});
console.log('setTimeout 外后', this.state.num);
};
答案页面显示 5,控制台:flushSync 前 0,flushSync 后 3,flushSync setState 后 3,setTimeout 外后 3,setTimeout 内 5
flushSync setState 合并 -> render -> 更新 -> setState -> render -> 更新 -> setTimeout -> render -> 更新
总结:
v18 之前的版本(legacy 模式)
- 处理
setState事件为 React 事件,会批量更新合并 state,执行一次render,更新 DOM,执行callback。 - 当为原生事件或
setState被setTimeout,promise等异步任务包裹时,会同步显示结果,多次执行render。 ReactDOM.flushSync同步条件下,其前面的 setState 和 内部的都会被合并(以内部的执行结果显示),执行一次 render。
v18 开启(concurrent 模式)
任何情况都会合并渲染,flushSync 包裹的调用后会立即渲染
props
组件间通信的重要方式。
展现形态有哪些?
如下代码:
function Hello() {
return <div>hello function</div>;
}
class PropsKind extends React.Component {
render() {
const { msg, changeMsgFromChild, Component, renderProps } = this.props;
const [slotChild1, slotChild2] = this.props.children;
return (
<div>
<div>{msg}</div>
<button onClick={changeMsgFromChild}>changeMsg</button>
{<Component />}
{renderProps()}
{slotChild1}
{slotChild2()}
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
msg: 'hi'
};
}
changeMsgFromChild = () => {
this.setState({ msg: 'from child' });
};
render() {
return (
<div>
<PropsKind
msg={this.state.msg}
changeMsgFromChild={this.changeMsgFromChild}
Component={Hello}
renderProps={() => <div>render props</div>}
>
<Hello />
{() => <div>show somethig depend props children</div>}
</PropsKind>
</div>
);
}
}
组件打印的 props :
| 形态 | 解释 |
|---|---|
简单变量msg={this.state.msg} | 传给组件内部渲染数据源 |
函数 changeMsgFromChild={this.changeMsgFromChild} | 子组件通知父组件的回调 |
组件 Component={Hello} | 传递组件 |
render props renderProps={() => <div>render props</div>}, {() => <div>show somethig depend props children</div>} | 共享代码技术 |
组件的子元素 <PropsKind><Hello /></PropsKind> | 插槽的作用 |
具有 render prop 的组件接受一个返回 React 元素的函数,并在组件内部通过调用此函数来实现自己的渲染逻辑。
HOC 高阶组件
HOC 是复用组件逻辑的一个手段。
使用场景:
- 作为属性代理
操作 props,可对 props 增加,删除。
function Hello(props){
console.log(props);
return <div>Hello</div>
}
const HOC = function (WrapperComponent) {
return class extends React.Component {
render() {
const newProps = {
...this.props,
name: 'HOC'
}
return <WrapperComponent {...newProps}/>;
}
};
};
const Hi = HOC(Hello);
class App extends React.Component {
render(){
return <Hi age="10" other="other"/>
}
}
上面代码将组件 props 扩展了 name 属性。
- 高阶组件获取 refs 引用是包装组件
class Hello extends React.Component {
constructor(props) {
super(props);
console.log(props);
const { getInstance } = props;
getInstance(this);
}
render() {
return <div>Hello</div>;
}
}
const HOC = function (WrapperComponent) {
return class extends React.Component {
render() {
const newProps = {
...this.props,
name: 'HOC'
};
return <WrapperComponent {...newProps} />;
}
};
};
const Hi = HOC(Hello);
class App extends React.Component {
componentDidMount() {
// HOC 包裹组件
console.log(this.node);
// 组件实例
console.log(this.childInstance);
}
render() {
return <Hi age="10" other="other" ref={node => (this.node = node)} getInstance={instance => (this.childInstance = instance)} />;
}
}
Hi 中 ref 拿到是包装组件,父组件中通过 getInstance 回调获取子组件实例,具体看打印信息:
- 将受控组件状态提升到高阶组件中维护
const HOC = function (WrapperComponent) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = { name: '' };
}
onNameChange = e => {
this.setState({
name: e.target.value
});
};
render() {
const newProps = {
name: {
value: this.state.name,
onChange: this.onNameChange
}
};
return <WrapperComponent {...newProps} {...this.props} />;
}
};
};
class Hello extends React.Component {
render() {
return (
<div>
{this.props.name.value}
<input {...this.props.name} />
</div>
);
}
}
const Hi = HOC(Hello);
class App extends React.Component {
render() {
return <Hi />;
}
}
- 改变组件容器布局样式
- 反向继承(通过继承拿到组件的属性方法)
const HOC = function(WrapperComponent){
return class extends WrapperComponent{
if(this.props.visible){
return super.render()
} else {
return <div>不展示</div>
}
}
}
总结:
高阶组件有两类:一类是属性代理;一类是反向继承;
使用注意事项
- 不要在 render 中使用 HOC,否则会导致组件及子组件状态丢失,每次都创建新的高阶组件;
- 手动拷贝静态方法,可用 hoist-non-react-statics 自动拷贝;
- HOC 的 ref 不会传到子组件,可用 forwardRef 转发处理;
- 不要在 HOC 内部修改原组件的原型属性,使用组合方式;
Hook
- 为什么有?
代码复用:Hook 可以在无需修改组件结构的情况下复用状态逻辑;
代码管理:Hook 将组件中相互关联的部分拆分更小的函数,并发强制按生命周期划分;
class 组件较为笨重,是面向对象的思想,把一堆逻辑封装在内部,Hook 是一种函数式编程,符合 UI = f(data);
- Hook 使用规则
只能在函数最外层调用 Hook。不要在循环,条件中使用。
只能在函数组件中调用。
API 使用
useState
[state, dispatch] = useState(initialData)
initData 为 state 初始值,可以是1、非函数类型;2、函数类型,返回值作为初始化值。
state 展示用。
dispatch 函数类型,更改 state 用。dispatch 参数可以为1、非函数,新值给 state;2、函数,入参为上一次的 state,返回值作为新的 state,更改视图(注意:不会自动合并更新对象)。另外会比较前后 state 是否更新,没更新,不会触发执行。
function App() {
const [count, setCount] = useState(0);
const click = () => {
setCount(count + 1);
setCount(n => n + 1);
// setCount(0);
console.log(count);
setTimeout(()=>{
setCount(count + 1);
console.log('timeout', count);
})
};
console.log('render', count)
return (
<div>
<p>{count}</p>
<button onClick={click}>change</button>
</div>
);
}
页面展示1,控制台:0,render 2, render 1, timout 0
视图的更新是通过函数的重新执行。所以 click 内部打印的 count 是初始值。
前面两个 setCount 触发一次渲染,count 为 2(0+1,1+1);setTimout 内 setCount 触发一次渲染,count 为(0 + 1)。
useEffect
useEffect(callback, dep)
初始化执行,依赖项 dep 变化时会触发回调 callback
function App() {
const [count, setCount] = useState(0);
useEffect(() =>{
console.log('useEffect', count);
}, [count])
const click = () => {
setCount(count + 1);
setCount(n => n + 1);
// setCount(0);
// console.log(count);
setTimeout(()=>{
setCount(count + 1);
// console.log('timeout', count);
})
};
console.log('render', count)
return (
<div>
<p>{count}</p>
<button onClick={click}>change</button>
</div>
);
}
//页面由 0 ->1, render 0 useEffect 0,change -> render 2 useEffect 2, render 1 useEffect 1
用作数据交互,事件监听,返回值可以用来清理状态,会在浏览器完成 DOM 绘制后执行,不阻塞渲染
useLayoutEffect
在 DOM 绘制之前触发。阻塞浏览器的绘制。
useReducer
useState 的替代方案。const [state, dispatch] = useReducer(reducer, initialArg, init);
reducer 形如
(state, action)=>newState;initialArg 初始值
init 惰性初始化 state ,值 init(initialArg),将计算 state 逻辑提取到 reducer 外部
适用于state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数 。
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
}
}
function App() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
{state.count}
<button onClick={() => dispatch({ type: 'increment' })}>increment</button>
<button onClick={() => dispatch({ type: 'decrement' })}>decrement</button>
</>
);
}
useContext
createContext 创建一个上下文,对外提供 Provider,消费者 Consumer。
useContext / Consumer 拿到最近 Provider 提供的 context。
const themes = {
theme1: {
color: 'red',
background: 'green'
},
theme2: {
color: 'green',
background: 'red'
}
};
const ThemeContext = createContext(null);
function ContextTestUse() {
const theme = useContext(ThemeContext);
return <button style={{ background: theme.background, color: theme.color }}>useContext!</button>
}
function ContextTestConsumer() {
return (
<ThemeContext.Consumer>
{theme => {
return (
<div>
<button style={{ background: theme.background, color: theme.color }}>consumer!</button>
</div>
);
}}
</ThemeContext.Consumer>
);
}
function App() {
const [contextValue, setContextValue] = useState(themes.theme1);
return (
<div>
<ThemeContext.Provider value={contextValue}>
<ContextTestUse />
<ContextTestConsumer />
</ThemeContext.Provider>
<button onClick={()=>setContextValue(themes.theme2)}>changeTheme</button>
</div>
);
}
useMemo useCallback
性能优化手段,会缓存值。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const memoizedCallback = useCallback(fn, [dep]);
第一次渲染都执行,之后依赖发生变化时触发。useMemo 缓存变量,useCallback 缓存函数
/* 用 useMemo包裹的list可以限定当且仅当list改变的时候才更新此list,这样就可以避免selectList重新循环 */
{useMemo(() => (
<div>{
selectList.map((i, v) => (
<span
className={style.listSpan}
key={v} >
{i.patentName}
</span>
))}
</div>
), [selectList])}
const Child = (props) => {
/* 只有初始化的时候打印了 子组件更新 */
console.log('子组件更新');
useEffect(() => {
props.getInfo('子组件');
}, []);
return <div>子组件</div>;
}
const DemoChildren = React.memo(Child);
// const DemoChildren = Child;
const App = ({ id }) => {
const [number, setNumber] = useState(1);
/* 此时usecallback的第一参数 (sonName)=>{ console.log(sonName) }
经过处理赋值给 getInfo */
const getInfo = useCallback(
sonName => {
console.log(sonName);
},
[id]
);
return (
<div>
{/* 点击按钮触发父组件更新 ,但是子组件没有更新 */}
{number}
<button onClick={() => setNumber(number + 1)}>增加</button>
<DemoChildren getInfo={getInfo} />
</div>
);
};
useRef
可用来获取 DOM 元素,组件实例。
返回的 ref 对象在组件的整个生命周期内保持不变,也就是说每次重新渲染函数组件时,返回的 ref 对象都是同一个。
function App() {
const inputEl = useRef(null);
const curFocus = () => {
// input DOM
console.log(inputEl.current);
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={curFocus}>Focus the input</button>
</>
);
}
useImpreativeHandle
配合 forwardRef ,自定义返回 ref 对象给父组件使用。
class Hello extends React.Component {
render() {
return <div>hello</div>;
}
}
function Hi(props, ref) {
const inputRef = useRef();
// ref 传过来的 回调返回新的 ref 给父组件
useImperativeHandle(ref, () => {
return {
focus: () => {
inputRef.current.focus();
}
};
});
return <div ref={inputRef}>Hi</div>;
}
// 转发 ref
const Hin = forwardRef(Hi);
function App() {
const refH = useRef(null);
const refF = useRef(null);
useEffect(() => {
console.log(refH);
console.log(refF);
});
return (
<div>
<Hello ref={refH} />
<Hin ref={refF} />
</div>
);
}