项目打包
项目打包是为了把整个项目都打包成最纯粹的js,让浏览器可以直接执行
打包命令已经在package.json里面定义好了
运行命令:npm run build
,执行时间取决于第三方插件的数量以及电脑配置
打包完之后再build文件夹下,这个文件夹下都是静态资源文件,需要web服务器运行
我们可以用serve来临时运行这个项目,看看好不好用
这是运行打包命令后的提示,提示我们可以用serve运行项目
尝试一下用serve,当然这种是临时的
访问一下
当然,实际开发肯定也不会只是用这种打包方式,这种打包太简陋了。一般都是用webpack,并且打包之后也不会用serve部署(这里只是临时看看),而是把静态文件交给后端的小伙伴,后端的小伙伴把文件部署在Nginx这种服务器上
类组件知识补充
setState
首先看一个案例证明setState操作是异步的
这是一个很简单的加和操作
首先我在加和的这个动作里console.log一下新的state
javascript
import React, { Component } from "react";
export default class Test extends Component {
state = {
count: 0,
};
add = () => {
const { count } = this.state;
this.setState({ count: count + 1 });
console.log("+1操作", this.state.count);
};
render() {
return (
<div>
<h1>当前count值{this.state.count}</h1>
<button onClick={this.add}>+1操作</button>
</div>
);
}
}
可以发现,setState并不是同步的操作,而是异步的
这里我们看一下setState更新状态的2种写法
javascript
(1). setState(stateChange, [callback])------对象式的setState
1.stateChange为状态改变对象(该对象可以体现出状态的更改)
2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
(2). setState(updater, [callback])------函数式的setState
1.updater为返回stateChange对象的函数。
2.updater可以接收到state和props。
4.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
总结:
1.对象式的setState是函数式的setState的简写方式(语法糖)
2.使用原则:
(1).如果新状态不依赖于原状态 ===> 使用对象方式
(2).如果新状态依赖于原状态 ===> 使用函数方式
(3).如果需要在setState()执行后获取最新的状态数据,
要在第二个callback函数中读取
上面的比较抽象,我们还是看代码,其他部分没变,集中在setState的部分
javascript
add = () => {
const { count } = this.state;
this.setState({ count: count + 1 }, () => {
// 在直接set完state后,立即执行一个回调函数
// 这个回调函数是等setState动作做完之后才执行的
console.log("+1操作执行了", this.state.count);
});
};
综上可以确定,setState是异步的操作
javascript
(1). setState(stateChange, [callback])------对象式的setState
//实际上对象式setState就是函数式setState的简写
add = () => {
const { count } = this.state;
this.setState({ count: count + 1 },()=>{
可以选传一个函数进来
})
};
javascript
(2). setState(updater, [callback])------函数式的setState
add = () => {
// 函数式的setState
this.setState((state, props) => {
return { count: state.count + 1 };
},()=>{
可以选传一个函数进来
});
};
LazyLoad懒加载
首先,代码中懒加载的机制是无论页面上有多少个路由标签。1个也好,100个也好,在页面加载出来的时候,都是一次性全部加载出来
页面一加载,路由就会把要用的组件 全都加载出来,后续用的话就不用单独请求加载了,但是这样如果组件过多就会造成负载很大,不太好。所以引入我们的懒加载机制
所以我们之前的import组件方式就都不要了,因为只要一用import导入就会自动加载,采用懒加载的导入方式
对比一下加载方式
javascript
// 这种import形式会默认一上来就加载,所以我们不能用这种了
// import About from "./pages/lazyload/About/About";
// import Home from "./pages/lazyload/Home/Home";
// 改用懒加载写法,并用变量接住
const About = lazy(() => import("./pages/lazyload/About/About"));
const Home = lazy(() => import("./pages/lazyload/Home/Home"));
但是此时改完懒加载后,点击运行会报错,提示没有用<Suspense fallback={标签}>
之所以强制用这个,是因为:我们的组件都是现用现请求,但这就带来了一个问题,网速慢我请求不来,或者请求不到数据怎么办?
我们就需要用一个兜底的方法,在这种请求失败或者网慢的时候给用户看。这个兜底的操作就是由Suspense
标签来做,注意,Suspense标签来自于React依赖
import React, { Component, Suspense, lazy } from "react";
javascript
// fallback引入的是兜底标签,比如模态窗口等等
<Suspense fallback={<h1>loading.....</h1>}>
<Switch>
<Route path="/xxx" component={Xxxx}/>
<Route path="/about" component={About}></Route>
<Route path="/home" component={Home}></Route>
<Redirect to="/login"/>
</Switch>
</Suspense>
此时点击某个route link,就会触发加载
改用慢速网,或者离线状态测试,可以发现兜底的标签已经显示出来了
Context
理解
一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信
比如A组件与C,D组件通信这种就叫祖组件通信,就需要用Context
AB就是正常的父子组件用props通信
再比如,组件树结构如下,现在想从根节点传递一个 userName 的属性到叶子节点 A D F,通过 props 的方式传递,会不可避免的传递通过 B C E,即使这些组件也没有使用这个 userName 属性。如果这样的嵌套树形结构有5层或10层,那么将是灾难式的开发维护体验。如果能不经过中间的节点直接到达需要的地方就可以避免这种问题,这时 Context api 就是来解决这个问题的。
Context api 是在组件树中传递数据但不用每层都经过的一种 api。
我们用完Context之后,就会出现类似这种的数据传递流程
首先使用类组件实现一个祖组件通信的例子(就是套娃,A用props给B,B用props给C,C用props给D)
javascript
import React, { Component } from "react";
export default class A extends Component {
state = {
data: "123456",
};
render() {
return (
<div>
<B data={this.state} />
</div>
);
}
}
class B extends Component {
render() {
return (
<div>
<C data={this.props.data} />
</div>
);
}
}
class C extends Component {
render() {
console.log("C组件", this.props.data);
return <div>C</div>;
}
}
但是这种写法非常不推荐,如果有n个组件,就要套n层,或者B组件中断了,就没法继续传递了,所以我们引入了Context传递
Context使用
javascript
1) 创建Context容器对象:
const XxxContext = React.createContext()
2) 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
<xxxContext.Provider value={数据}>
子组件
</xxxContext.Provider>
3) 后代组件读取数据:
//第一种方式:仅适用于类组件
static contextType = xxxContext // 声明接收context
this.context // 读取context中的value数据
//第二种方式: 函数组件与类组件都可以
<xxxContext.Consumer>
{
value => ( // value就是context中的value数据
要显示的内容
)
}
</xxxContext.Consumer>
创建一个 Context 对象。当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值。
这里以第二种方式为例,分文件跨组件调用
A,B组件在同一文件,C组件单独一个文件
A,B组件
javascript
import React, { Component } from "react";
import C from "./C";
export const MyContext = React.createContext();
export default class A extends Component {
state = {
data: "123456",
};
render() {
return (
<div>
<MyContext.Provider value={this.state}>
{/* 不再需要声明props了 <B data={this.state} /> */}
<B />
</MyContext.Provider>
</div>
);
}
}
class B extends Component {
render() {
return (
<div>
{/* 不再需要声明props了 <C data={this.state} /> */}
{/* 这里用到C只是为了桥接祖孙组件的关系 */}
<C />
</div>
);
}
}
C组件
javascript
import React, { Component } from "react";
import { MyContext } from "./A";
export default class C extends Component {
render() {
return (
<div>
C组件
{/* 导入A定义好的MyContext,并用.Consumer定义为消费者
在这个<MyContext.Consumer>标签里就可以使用祖组件的传值了,value即为全部信息,当props用即可 */}
<MyContext.Consumer>
{(
value // value就是MyContext中的value数据
) => console.log(value)}
</MyContext.Consumer>
</div>
);
}
}
这样就可以实现祖组件与孙组件之间通信
但是实际上,在应用开发中一般不用context, 一般都用它的封装react插件
而且后续还有useContext,所以这里只做补充知识了解
Fragment
编译完实际上就是一个<></>
空标签(编译之后就什么都没有了)
效果上二者等价,但是Fragment的意义是可以传一个参数,而完全空的标签是无法传递参数的
可以传递参数也就成为了Fragment存在的意义(比如循环的时候,传一个)
javascript
import { Fragment } from 'react'
//类似于一个<div>
<Fragment></Fragment> //可以添加一个属性 'key',用于遍历比如 <Fragment key={i}>{v}</Fragment>
<></> //不能添加任何属性
编译之后就自动消失了
举一个遍历时用Fragment传入key的操作
javascript
export default function App() {
const arr = React.useState([
{ id: "1", name: "a" },
{ id: "2", name: "b" },
{ id: "3", name: "c" },
]);
return (
<Fragment>
{arr.map((id, name) => {
return <Fragment key={id}>name:{name}</Fragment>;
})}
</Fragment>
);
}
遍历完成后,所有的标签有了key的同时,Fragment也自动去掉了
从作用上来看:
可以不用必须有一个真实的DOM根标签了,同时也可以满足空标签传递一个变量的这种需求
PureComponent
父组件里用到了子组件,父组件每次state改变,导致重新render的时候,就会触发子组件渲染。但其实有些时候,子组件没有用到父组件的state,就不需要重新渲染,所以这种重新渲染是浪费的,可以看这个例子
javascript
import React, { Component } from "react";
export default class A extends Component {
state = {
data: "123456",
};
updateState = () => {
// 手动触发更新state
this.setState({ data: "123" });
};
render() {
console.log("父组件render调用");
return (
<div>
{/* 我们这里只传递了一个固定值,没有用到state的值 */}
<B data="data" />
{/* 手动触发更新state */}
<button onClick={this.updateState}>UPDATE</button>
</div>
);
}
}
// 子组件
class B extends Component {
render() {
console.log("子组件render调用");
return <div>B组件</div>;
}
}
这种渲染的控制阀门,可以参考之前我们生命周期里学习的
shouldComponentUpdate()
这个函数,这个函数可以接收到两个值(nextProps, nextState)
,这两个值就是未来要用于更新的值,我们可以将其与当前值(state,props)比较,判断是否需要开启更新的阀门(true更新,false不更新)
比如:
javascript
shouldComponentUpdate(nextProps, nextState) {
// nextProps用不用取决于你想不想判断,不想判断不传也行
// xxx是state里的某个属性名
// 如果判断相同,则不需要更新,所以true的条件取反为false,关闭更新阀门
return !this.state.xxx === nextState.xxx;
}
from之前的笔记:
shouldComponentUpdate(nextProps, nextState)
在setState以后,state发生变化,组件会进入重新渲染的流程时执行的逻辑。在这个生命周期中return false可以阻止组件的更新,主要用于性能优化。
所以针对于这种情况,React官方已经提供好了,我们直接继承PureComponent
即可,已经给我们重写好了
javascript
import React, { PureComponent } from "react";
// 继承PureComponent
export default class A extends PureComponent {
state = {
data: "123456",
};
updateState = () => {
// 手动触发更新state
// 注意,这里并不是给state设置为空,而是setState函数里传了个空对象进去
// 代表什么都不更新的意思
this.setState({ data: "123" });
};
render() {
console.log("父组件render调用");
return (
<div>
{/* 我们这里只传递了一个固定值,没有用到state的值 */}
<B data="data" />
{/* 手动触发更新state */}
<button onClick={this.updateState}>UPDATE</button>
</div>
);
}
}
// 继承PureComponent
class B extends PureComponent {
render() {
console.log("子组件render调用");
return <div>B组件</div>;
}
}
总结PureComponent
原始Component的2个问题
只要执行setState(),即使不改变状态数据(setState传一个空对象也会触发state更新,只不过state的内容不动), 组件也会重新render() ==> 效率低
只当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低
效率高的做法
只有当组件的state或props数据发生改变时才重新render()
原因
Component中的shouldComponentUpdate()总是返回true
解决
办法1:
重写shouldComponentUpdate()方法
比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
办法2:
使用PureComponent
PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true
注意:
只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false
不要直接修改state数据, 而是要产生新数据
项目中一般使用PureComponent来优化
再次注意:PureComponent 只是进行state和props数据的浅比较(只比较内存地址)。如果只是数据对象内部数据变了,而数据对象本身的引用地址没有变化,则被认为没有变化,返回false。不要直接修改state数据, 而是要产生新数据(新对象,新内存地址),再更新state
render props(插槽技术,封装组件必用)
场景:想给某个组件标签传入一个组件
即这种类似场景:
A标签引入了B,B在A组件里显示,结构上看起来确实是A与B为父子组件。但这就带来了一个问题,表面上是AB父子可以通过props传参,但实际上,这个操作只能在A标签里写,并且传什么值只能写死,没办法灵活修改。所以就会出现render props这种技术
javascript
export default class Parent extends Component {
render() {
return (
<div className="parent">
Parent
<A>
<B></B>
</A>
</div>
);
}
}
简单来说,就是运用箭头函数的return操作,给A标签里传一个属性,这个属性return一个B标签,在目标组件里,调用这个属性的返回值,即:
javascript
// 这个组件参数的调用,就是传入组件的插槽插进来的地方!!!
// 这其实就是jsx语法
{this.props.render()}
注意,我们一般把这个传参的属性叫render,这只是约定俗成,其实叫啥都行
修改一下:,此时在祖组件Parent里面一看就知道A与B的父子关系,同时知道了A中用到了B做插槽
javascript
export default class Parent extends Component {
render() {
return (
<div className="parent">
Parent
<A render={() => <B/>} />
</div>
);
}
}
export default class A extends Component {
render() {
return (
<div className="A">
这里是A标签
<br />
{/*
调用render属性,一定要加括号,变成函数返回值
这样才能拿到传入的B标签
*/}
// 这个组件参数的调用,就是B的插槽插进来的地方!!!
{this.props.render()}
</div>
);
}
}
运行一下,发现B组件已经被插入了A组件的插槽里了
个人感觉就是类组件属性的灵活调用,好处在于我们组件有了插槽,可以灵活在祖组件替换,不必在父组件里再写死组件。同时传参也可以正常进行
插槽传参
传参就是在箭头函数的基础上加入了参数传递,很好理解
javascript
export default class Parent extends Component {
render() {
return (
<div className="parent">
Parent
<A render={(data) => <B data={data} />} />
</div>
);
}
}
export default class A extends Component {
state = {
data: "这是A传来的参数",
};
render() {
return (
<div className="A">
这里是A标签
<br />
{/*
插槽,并且传入A组件的参数给插槽插进来的组件,不再局限于某一固定组件
*/}
{this.props.render(this.state.data)}
</div>
);
}
}
export default class B extends Component {
render() {
return (
<div className="B">
B标签做插槽!!!
<br />
接收到A传来的参数: {this.props.data}
</div>
);
}
}
参数传递顺序:
javascript
A组件:{this.props.render(this.state.data)}
Parent组件接收A组件传来的参数: <A render={(data) => <B data={data} />} />
B组件解析props: {this.props.data}
总结render props
如何向组件内部动态传入自定义组件标签(插槽技术)
Vue中:
使用slot技术, 也就是通过组件标签体传入结构 <A><B/></A>
React中:
使用children props: 通过组件标签体传入结构
使用render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性
children props
<A>
<B>xxxx</B>
</A>
{this.props.children}
问题: 如果B组件需要A组件内的数据, ==> 做不到 ,因为这种解析仅限于H5的标签children属性,React并没有办法直接支持
render props
A组件:{this.props.render(内部state数据)}
Parent组件接收A组件传来的参数: <A render={(data) => <B data={data} />} />
B组件解析props: 读取A组件传入的数据显示{this.props.data}
错误边界
说白了,就是个兜底的方法,当组件的子组件出现错误时,不至于崩掉,将错误控制在一个小小的子组件里,不至于让整个应用崩掉
当组件的子组件出现错误时会调用,也就是始终去找出错组件的父组件去处理
static getDerivedStateFromError(error)
的钩子,并且得到错误信息,并且将错误信息return到state里,触发页面渲染,做出相应的处理,比如错误页面,弹窗等等友好提示
但是注意,这个东西只能在prod生产环境生效,对于dev模式下,只会给你闪一下处理的页面,然后就继续该报错报错,因为要在dev模式下告诉程序员哪里出错了,才能做出相应的处理
同时,错误边界的捕捉,仅限于生命周期函数,比如render,componentDidMount......这种生命周期函数里发生的错误
javascript
class Children extends Component {
// 比如这种胡乱调用的某个函数(非生命周期的),就不会被catch住
test()
render() {
return (
<div>
</div>
);
}
}
例子:
javascript
import React, { Component } from "react";
export default class Parent extends Component {
state = {
// 异常Flag
hasError: false,
};
static getDerivedStateFromError(error) {
// 子组件生命周期函数里出现的异常会在这里被捕捉,同时获取异常信息
console.log(error);
// 在render之前触发
// 返回新的state,触发组件重新渲染
return {
hasError: true,
};
}
render() {
return (
<div>
<div>
父组件
<br />
{/* 根据子组件是否有错误,来判断显示内容 */}
{this.state.hasError ? <>子组件网络出错,请重试</> : <Children></Children>}
</div>
);
}
}
class Children extends Component {
state = {
// 空的内容,想map遍历直接报错了
// users: [
// { id: "123", name: "Tony" },
// { id: "456", name: "Jerry" },
// ],
};
render() {
return (
<div>
子组件
{this.state.users.map((user) => {
return <div key={user.id}>name:{user.name}</div>;
})}
</div>
);
}
}
开发模式下的控制台:
生产环境下例子:
componentDidCatch钩子(属于生命周期钩子,但不常见)
这个钩子一般是和getDerivedStateFromError配合使用,每次出现错误就会调用这个函数,一般用于统计错误次数以及错误信息,并打包发给后端,同样也是生产环境下才生效
javascript
// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
console.log(error);
// 在render之前触发
// 返回新的state
return {
hasError: true,
};
}
componentDidCatch(error, info) {
// 统计页面的错误。发送请求发送到后台去等一系列操作都可以在这做
console.log("componentDidCatch钩子运行", error, info);
}
组件通信方式总结
组件间的关系
- 父子组件
- 兄弟组件(非嵌套组件)
- 祖孙组件(跨级组件)
几种通信方式
1.props:
(1).children props
(2).render props
2.消息订阅-发布:
pubs-sub、event等等
3.集中式管理:
redux、dva等等
4.conText:
生产者-消费者模式
比较好的搭配方式
父子组件:props
兄弟组件:消息订阅-发布、集中式管理
祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)
React-Hooks
React16.8之前,函数式组件本身并没有this ,打印一下组件的this结果是undefined,因此函数式组件无法通过this.setState
等操作本身状态,或者操作生命周期,但在16.8之后,React提出来Hooks的概念。函数式组件就可以使用Hooks来操作自身的状态,生命周期等等,函数式组件就突然变得香了起来。
useState
hooks
解决了函数式组件和类式组件的差异,让函数式组件拥有了类式组件所拥有的 state
,同时新增了一些 API ,让函数式组件,变得更加的灵活
首先我们需要明确一点,函数式组件没有 自己的 this
,现在常用的组件都是函数式组件,不再是类式组件
js
function Demo() {
const [count, setCount] = React.useState(0)
console.log(count, setCount);
function add() {
setCount(count + 1)
}
return (
<div>
<h2>当前求和为:{count}</h2>
<button onClick={add}>点我加1</button>
</div>
)
}
export default Demo
利用函数式组件完成的 点我加1 案例
这里利用了一个 Hook :useState
它让函数式组件能够维护自己的 state
,它接收一个参数 ,作为初始化 state
的值,赋值给 count
,因此 useState
的初始值只有第一次有效 ,它所映射出的两个变量 count
和 setCount
我们可以理解为 setState
来使用
useState 能够返回一个数组,第一个元素是 state ,第二个是更新 state 的函数
javascript
// 打印一下这个Hooks的内容
const [count, setCount] = React.useState(0);
console.log(count, setCount);
控制台输出:
0就是我们传入初始化的count,后面的函数就是setCount的函数
count
是初始化的值,而 setCount
就像是一个 action
对象驱动状态更新
我们可以通过 setCount
来更新 count
的值,setCount可以传入两种形式(直接传值 或 箭头函数)
js
setCount(count + 1)
除了这种直接传值的操作,我们还可以用箭头函数的写法,二者等价,视情况使用
javascript
setCount((count) => {
// 把计算结果return给count
return count + 1;
});
总结useState
javascript
(1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
(2). 语法: const [xxx, setXxx] = React.useState(initValue)
(3). useState()说明:
参数: 第一次初始化指定的值在内部作缓存,读取的时候可以直接调用
返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
(4). setXxx()2种写法:
setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
useEffect
在类式组件中,提供了一些声明周期钩子给我们使用,我们可以在组件的特殊时期执行特定的事情,例如 componentDidMount
,能够在组件挂载完成后执行一些东西(这是类组件生命周期常用的写法)
在函数式组件中也可以实现,它采用的是 useEffect Hook
,它的语法更加的简单,同时融合了 componentDidUpdata
生命周期,极大的方便了我们的开发
注意点:useEffect 是在render结束之后才执行的。
js
React.useEffect(() => {
console.log('被调用了');
})
由于函数的特性,我们可以在函数中随意的编写函数,这里我们调用了 useEffect
函数,这个函数有多个功能
当我们像上面代码那样使用时,它相当于 componentDidUpdata
和 componentDidMount
一同使用,也就是在组件挂载和组件更新的时候都会调用这个函数
它还可以接收第二个参数 ,这个参数表示它要监测的数据,也就是他要监视哪个数据的变化,分三种情况传递参数
当我们监听页面上所有的状态变化的时候,第二个参数上就可以什么都不传,当页面上有任意状态变化,useEffect就会执行
js
React.useEffect(() => {
console.log('被调用了');
})
当我们不需要监听任何状态变化的时候 ,我们可以就传递一个空数组 ,这样它就能当作 componentMidMount
来使用(比如一上来就加载数据这种操作),这样我们只有在组件第一次挂载的时候触发
js
React.useEffect(() => {
console.log('被调用了');
}, [])
当我们需要监听一个数据或多个数据变化的时候,我们也可以在数组里选择指定的数据来进行监控,当监听的数据发生变化时,相应的函数体就会被执行
js
React.useEffect(() => {
console.log('被调用了');
}, [被监听的变量1,被监听的变量2,被监听的变量3...])
当我们想要在卸载一个组件之前进行一些清除定时器 的操作,在类式组件中 ,我们会调用生命周期钩子 componentDidUnmount
来实现,但在函数式组件中 ,我们的写法更为简单,我们直接在 useEffect
的第一个参数传入返回值实现即可
也就是useEffect的return值,当组件卸载时,这个函数会被执行。这可以帮助我们取消或清理副作用,比如取消网络请求、关闭定时器等。如果不需要清理副作用,useEffect可以不返回任何值。
测试一下
手动实现卸载
js
function unmount() {
ReactDOM.unmountComponentAtNode(document.getElementById("root"))
}
卸载前输出
js
React.useEffect(() => {
console.log('被调用了');
return () => {
console.log('我要被卸载了');
}
}, [count])
useEffect里写了return就可以实现在组件即将被卸载的时候输出
因此 useEffect
相当于三个生命周期钩子
[
componentDidMount()
] 组件刚挂载好就调用,第二个参数传入空数组的场景[
componentDidUpdate()
] 某一个state发生变化时调用,第二个参数传入某个state来监听的场景[
componentWillUnmount()
] 组件即将卸载时调用,再useEffect里写了return的场景
除此以外的就是监听普通数据的变化,所以不算在生命周期钩子里面
总结useEffect
javascript
(1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
(2). React中的副作用(useEffect)操作:
发ajax请求数据获取
设置订阅 / 启动定时器
手动更改真实DOM
(3). 语法和说明:
useEffect(() => {
// 在此可以执行任何带副作用操作
return () => { // 在组件卸载前执行(可选,非必须项目)
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
}
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
(4). 可以把 useEffect Hook 看做如下三个生命周期函数的组合
[componentDidMount()] 组件刚挂载好就调用,第二个参数传入空数组的场景
[componentDidUpdate()] 某一个state发生变化时调用,第二个参数传入某个state来监听的场景
[componentWillUnmount() ] 组件即将卸载时调用,再useEffect里写了return的场景
useLayoutEffect
和 useEffect
很类似,用的不多,一般都是用useEffect。官方也并不推荐用useLayoutEffect
,会阻塞浏览器重新绘制,从而影响性能。useLayoutEffect
里的操作都执行完毕之后,才会继续让浏览器往下走,所以会影响性能。
它的作用是:在 DOM 更新完成之后执行某个操作
注意:
执行时机在
useEffect
之前,其他都和useEffect
都相同
useEffect
执行时机在 render 之后
useLayoutEffect
执行时机在 DOM 更新之后,比render还要晚一点
打印一下运行顺序
javascript
React.useEffect(() => {
console.log("useEffect被调用了");
}, []);
React.useLayoutEffect(() => {
console.log("useLayoutEffect被调用了");
}, []);
总的来说不推荐用,了解即可。
useRef
当我们想要获取组件内的信息时,在类式组件中,我们会采用 ref
的方式来获取。在函数式组件中,我们可以采用也可以采用 ref
但是,我们需要采用 useRef
函数来创建一个 ref 容器,保存目标节点信息,这和 createRef
很类似。
js
const myRef= React.useRef();
// 获取到input标签的全部信息,并且绑定给myRef上
<input type="text" ref={myRef} />
...butten触发打印
获取 ref 值,即可打印节点里的信息
js
function show() {
console.log(myRef)
}
即可成功的获取到 input 框中的值,包括value等一系列的目标标签属性
打印标签全部属性
总结useRef
javascript
(1). Ref Hook可以在函数组件中存储/查找 组件内的标签或任意其它数据
(2). 语法: const refContainer = useRef()
(3). 作用:保存标签对象并随变化更新,功能与类组件的React.createRef()一样
useContext
相比于Context,useContext是为函数式组件打造的Hook
举个例子:
祖组件内容:
javascript
// 句柄创建一次即可,规定作用范围,将需要传递的孙组件一并传入进去
export const MyContext = React.createContext();
export default function App() {
return (
<BrowserRouter>
{/* 确定作用范围的同时,value属性传入参数 */}
<MyContext.Provider value={"123456"}>
<A />
</MyContext.Provider>
</BrowserRouter>
);
}
祖孙组件通过A组件来桥接,即 App祖组件 -> A子组件 -> B孙组件
在孙组件中,通过 useContext(祖组件中定义的Context)
来获取数据
javascript
import React from "react";
// 导入定义好的范围句柄,用于Hooks接收
import { MyContext } from "../../App";
export default function B() {
// Hoooks接收
const num = React.useContext(MyContext);
return <div>B组件接收:{num}</div>;
}
useMemo
待更新