6. useMemo—似计算属性
useMemo和useCallback的作用十分类似,只不过它允许记住任何类型的变量(useCallback只记住函数)。当改变其他变量时,普通函数都会运行,它返回的结果并没有改变。这个时候就可以使用useMemo将函数的返回值缓存起来。用来优化代码。
import React, { useState } from "react";
export default function App() {
let [msg, setMsg] = useState("hello");
let [n, setN] = useState(2);
// 求变量(数字)是否为素数
let fn = (n) => {
console.log("计算素数", n);
n = parseInt(n);
if (n <= 3) {
return n > 1;
}
for (let i = 2; i < n; i++) {
if (n % i === 0) {
return false;
}
}
return true;
};
return (
<div>
<h2>App</h2>
{<p>{n}是---{fn(n) ? "素数" : "非素数"}</p>}
<button onClick={() => setN(13327)}>changeN</button>
<p>{msg}</p>
<button onClick={() => setMsg("world")}>changeMsg</button>
</div>
);
}
解决办法:useMemo来记录函数
// 添加
let sushu = useMemo(() => {
let fn = (n) => {
console.log("计算素数", n);
n = parseInt(n);
if (n <= 3) {
return n > 1;
}
for (let i = 2; i < n; i++) {
if (n % i === 0) {
return false;
}
}
return true;
};
return fn(n);
}, [n]);
}
// 使用
<p>{n}---{sushu ? "素数" : "非素数"}</p>
补充点:react中,为什么组件return出来的虚拟模板对象要加小括号?
答:在js语法中函数内部的return的用法
不写 默认函数调用完毕 生成undefined
写数据直接量
写表达式,表达式执行结果作为函数的返回值
return后面不写 <==> 生成undefined
return语法注意点:必须写在return后面,不能换行(因为js语法中换行代表语句结束)
所以,如果不加小括号,且换行了,js语法就默认换行为语句结束,返回undefined,而不是返回整个模板。
7. 受控组件—似双向数据绑定
非受控组价:输入框内容为只读,不能手动修改,只能用代码改。
import React, { useState } from "react";
export default function Box() {
let [email, setEmail] = useState("222");
let login = () => {
console.log(email);
setEmail("6666");
};
return (
<div>
<h3>Box</h3>
<input type="text" value={email} />
<button onClick={login}>get</button>
</div>
);
}
受控组件:类似双向数据绑定。onChange事件 + event.target.value
import React, { useState } from "react";
export default function Box() {
let [email, setEmail] = useState("222");
let login = () => {
console.log(email);
};
return (
<div>
<h3>Box</h3>
<input type="text" value={email} onChange={()=>setEmail(event.target.value)} />
<button onClick={login}>get</button>
</div>
);
}
8. useContext—多层状态传递
1. 定义
允许在父级组件和底下任意层次的子组件之间传递状态。在函数组件中使用useContext来使用。Context。
2. 方法
创建上下文时,需要将这个ctx单独放在一个js文件中,要使用上下文的就导入进去。使用React.createContext来创建。
import React from "react";
let ctx = React.createContext({})
export default ctx;
父组件提供: <ctx.Provider value={{ 提供的对象 }}></ctx.Provider>
后代组件使用:let obj = useContext(ctx); <h3>Box--{obj.age}</h3>
// App父组件
import React, { useState } from 'react'
import ctx from './ctx.js';
import Box1 from "./Box1.jsx";
import Box2 from "./Box2.jsx";
export default function App() {
let [msg, setMsg] = useState('hello');
// ctx.Provider是一个组件
return (
<ctx.Provider value={[msg, setMsg]}>
<div>
<h2>App--{msg}</h2>
<Box1></Box1>
<Box2></Box2>
</div>
</ctx.Provider>
);
}
// Box1子组件
import React, { useContext } from 'react'
import ctx from "./ctx.js";
import Box11 from "./Box11.jsx";
export default function Box1() {
let [msg, setMsg] = useContext(ctx);
let chengeMsg = () => {
setMsg('world')
};
return (
<div>
<h3>Box1---{msg}</h3>
<Box11></Box11>
<button onClick={chengeMsg}>chengeMsg</button>
</div>
);
}
// Box11 孙组件
import React, { useContext } from 'react'
import ctx from "./ctx.js";
export default function Box11() {
let [msg, setMsg] = useContext(ctx);
return (
<div>Box11--{msg}</div>
)
}
// Box2组件 和Box1同级
import React, { useContext } from "react";
import ctx from "./ctx.js";
export default function Box2() {
let [msg, setMsg] = useContext(ctx);
return (
<div>
<h3>Box2---{msg}</h3>
</div>
);
}
上面案例可以实现子组件Box修改父组件App传来的值,并属性页面中所有用到msg的组件。
3. 提供多个上下文时
父组件提供多个数据时,可以使用嵌套来实现(只需要把上下文创建并引入即可),也可以把多个上下文放在同一个上下文中,需要使用哪个状态就通过点语法来取。比如:
// App父组件
import React, { useState } from 'react'
import ctx from './ctx.js'
import ctx2 from "./ctx2.js";
import ctx3 from "./ctx3.js";
import Box1 from "./Box1.jsx"
import Box2 from "./Box2.jsx"
import Box3 from "./Box3.jsx"
export default function App() {
// let [state, setSate] = useState({cardata:{ title: "电脑", id: 21 },userdata:{ name:"rosy"}})
let [age, setAge] = useState(33);
let [msg, setMsg] = useState({ name: "jack" });
return (
<ctx2.Provider value={[msg, setMsg]}>
<ctx.Provider value={[age, setAge]}>
<div>
<h2>App--{age}</h2>
<Box1></Box1>
<Box2></Box2>
</div>
</ctx.Provider>
</ctx2.Provider>
);
}
// Box1
import React from 'react'
import Box11 from "./Box11.jsx"
export default function Box1() {
return (
<div>
<h2>Box1</h2>
<Box11></Box11>
</div>
)
}
// Box11 孙组件
import React,{useContext} from 'react'
import ctx2 from './ctx2.js'
export default function Box11() {
let [msg, setMsg] = useContext(ctx2);
return (
<div>Box11--{msg.name}</div>
)
}
// Box2子组件
import React, { useContext } from "react";
import ctx from "./ctx.js";
export default function Box2() {
let [age, setAge] = useContext(ctx);
return (
<div>
<h3> Box2--{age}</h3>
</div>
);
}
当一个函数组件使用了useContex时,它就订阅了这个ctx的变化,这样当ctx.Provider的value发生变化的时,这个组件就会被重新渲染。上面有介绍,当有多个上下文时可以使用同一个ctx里面,而不同的子组件可能只关心该ctx的某一部分状态,当ctx里面的任意值发生变化的时候,无论这些组件用不用到这些数据它们都会被重新渲染,这样会造成无用渲染,影响性能。接下来介绍三种解决无用渲染的方法
9. React.memo
1. 拆上下文
2. useMemo
不需要用到上下文的函数用useMemo来缓存,写上依赖项。
import React, { useContext, useMemo, useState } from "react";
import ctx from "./ctx";
export default function Box3() {
let [state, setSate] = useContext(ctx)
let [n, setn] = useState(100)
return useMemo(() => {
console.log("刷新模板--只有数据变化才会刷新")
return (
<div>
<h2>Box3</h2>
<p>{state.userdata.name}</p>
</div>
)
}, [state.userdata.name]);
}
3. React.memo
import React, { useContext, useMemo, useState } from "react";
import ctx from "./ctx";
let Box = React.memo(({ userinfo }) => {
console.log("刷新模板--无用渲染");
return (
<div>
<h2>box3</h2>
<p>{userinfo}</p>
</div>
);
});
export default function Box3() {
let [state, setSate] = useContext(ctx);
return <Box userinfo={state.userdata.name}></Box>;
}