React 学习笔记
安装 react 并初始化
1、安装:npm install -g create-react-app
2、创建 hello-react 目录并初始化:npx create-react-app hello-react
注意:
- 目录名不允许有大写字母
- 初始化过程比较慢,大约需要 5-10 分钟
3、启动项目:cd hello-react、npm start
默认将启动:http://localhost:3000
自定义组件基础知识
1、自定义组件和入口文件,顶部都需要引入 “react:import React from ‘react’”;
2、自定义组件必须以大写字母开头、默认网页原生标签还以小写开头。请注意这里表述的”默认网页原生标签”本质上并不是真实的原生网页标签,他们是 react 默认定义好的、内置的自定义组件标签,只不过这些标签刚好和原生标签的作用,功能,名称一模一样而已。
3、自定义组件 “render(){return
4、如果不希望设定最外层的标签,那么可以使用 react(16+版本)提供的占位符 Fragment 来充当最外层标签;
1 |
|
在最新的 react 版本中,可以直接使用”<></>”来代替 Fragment。其中<>唯一可以拥有的属性为 key。即”< key=’xxx’></>”
5、如果你写了 “constructor(props){super(props);}”但是还没有写 “this.state={}”,默认会报错误警告:”Useless constructor. eslint(no-useless-constructor),constructor” 中加上 “this.state={}”即可消除警告。
6、希望数据发生改变,忘掉 DOM 操作,改成数据操作(setState())。
7、使用数组 map 循环更新 li,一定要给 li 添加对应的 key 值,否则虽然正常运行,但是会报错误警告。不建议直接使用 index 作为 key 值。
8、永远不要直接操作修改 this.state.xxx=xxx,一定要用 setState()来修改。
9、先复制出一份 this.state.xxx 的值,然后修改,修改完成后再通过 setState()赋值。
10、在最新的 react 版本中,为了提高更新性能,推荐采用异步的方式更新数据。具体使用方式为:
1 |
|
其中参数 prevState 指之前的数据值(即 this.state),return 的对象指修改之后的数据值。pervState 参数还可以省略,即 setState(() => {return {xxx}});
setState()还可以增加第 2 个参数(回调函数),即当异步的 setState 更新完成后执行的函数。
1 |
|
上述代码中的第 2 个参数()=>{xxxxx}就是异步执行(更新)完毕后的回调函数。
异步的目的是为了优化更新性能,react 短期内发现多条 state 发生修改,那么他会将所有修改合并成一次修改再最终执行。
11、在 “render(){return
12、给标签添加样式时,推荐使用 className,不推荐使用 class。如果使用 class 虽然运行没问题,但是会报错误警告,因为样式 class 这个关键词和 js 中声明类的 class 冲突。类似的还有标签中 for 关键词,推荐改为 htmlFor。
13、直接将 xxx 赋值给某标签,作为该标签的内容时会进行转义(若 xxx 为”
xxx
“,则以纯文本形式展示)。若不想转义,则在属性中设置1 | dangerouslySetInnerHTML={{__html:xxx}} |
,如无必要,尽量不要这样设置,容易造成 xss 攻击。
14、通常情况下,react 是针对组件开发,并且只负责对 html 中某一个 div 进行渲染,那么意味着该 html 其他标签不受影响,这样引申出来一个结果:一个 html 既可以使用 react,也可以使用 vue,两者可以并存。
15、为了方便调试代码,可以在谷歌浏览器中安装 React Developer Tools 插件。安装后可在谷歌浏览器调试模式下,查看 component 标签下的内容。 若访问本机 react 调试网页则该插件图标为红色、若访问导出版本的 React 网页则该插线显示为蓝色、若访问的网页没使用 react 框架则为灰色。
16、给组件设定属性 “ref={(xxx) => {this.xxx=xxx}}”,之后如果写处理函数中,想获得 DOM 元素可以不需要写 eve.target,而是直接写 this.xxx 即可找到该 DOM。但是不建议这样操作,还应该以更改数据而非直接操作 DOM 为原则。除非你想给该 DOM 元素设置焦点。
17、给组件设定属性,只有属性名没有属性值,那么默认 react 会将该属性值设置为 true。在 ES6 中如果只有一个属性对象没有属性值,通常理解为该属性名和属性值是相同的。 为了避免混淆,不建议不给属性不设置属性值。
18、属性设置中,使用{…props} 表示展开 props(props 可以是自己定义的对象,也可以值默认传递过来的参数 props),并继续向下传递。
19、若组件 “render(){return xxx}中 xxx” 为函数,则会将运行结果作为组件内容输出出去,但是如果 xxx 的值为 true、false、null、undefined 中的任意一种,这种输出结果是允许的,但是不会进行任何渲染。 结合这个特性,可以做条件渲染,例如 “{isxxx &&
请注意:aa && bb 这种 JS 原生语法,只有 aa 为布尔值(true 或 false)时才会按照预期来确定是否返回 bb 还是 false。aa 是数字(数字 1 除外)或字符串或空数组(只要不是布尔值),那么就都会返回 bb(而不是返回 false),为了保证每次都是预期返回,建议要用 Boolean(aa) 形式来将 aa 转化为布尔值。
相反,如果在输出时想输出布尔值 boo 为字符串,那么应该用 String(boo)将布尔值转化为字符串”true”或”false”
20、使用”<React.StrictMode></React.StrictMode>”标签包括元素,表示被包裹的元素将使用严格模式(若有非严格模式的错误,将会有对应错误警告提示)。”<React.StrictMode>”标签并不会显示在前台页面中,并且该元素仅在开发模式下启作用,在生产模式下将忽略被包裹元素的非严格模式下的错误提示。
21、ReactDOM.createPortal()用来将元素渲染到任意 DOM 元素中(包括顶级组件之外的其他 DOM 中)。
“纯函数” 概念解释
JS 中定义的所有函数都可以增加参数,所谓”纯函数”是指函数内部并未修改过该参数的函数。
例如以下函数:function myFun(a){let c=a },该函数内部从未更改过参数 a,那么这个函数就是纯函数。
反例,非纯函数 例如:function myFun(a){a=a+2; let c=a},该函数内部修改过参数 a,那么这个函数就不再是纯函数了。
纯函数的特殊意义是什么?
因为纯函数内部从不会直接修改参数,那么无论运行多少次,执行结果永远是一致的。
若仅仅有一个函数,那么也无所谓,但是如果有多个函数都是都需要调用执行同一个变量(参数),为了确保多个函数执行结果是符合预期的,那么就要求每个函数都不能在自己内部修改该变量(参数)。
在 react 中定义的 this.state 就会被作为参数供内部多个函数使用,react 要求任何函数不能直接修改 this.state 的值,确保各个函数在收到参数 this.state 时是一致的 this.state。
这就是为什么 react 不允许直接修改 this.state 的原因。
“受控组件” 概念解释
像 input、select、textarea、form 等将自身 value 与 state 进行绑定的组件,称之为受控组件。
“受控”即这些组件的可以值受到 state 的控制。
与之对应的是”非受控组件”,即该组件对应的值并不能被 state 控制。
例如”<input type=’file’/>“,该组件的值为用户选中本地的文件信息,该值并不能直接通过 state 来进行控制(设置),因此该组件属于”非受控组件”。
“声明式开发” 概念解释
“声明式开发”:基于数据定义和数据改变,视图层自动更新。
“命令式开发”:基于具体执行命令更改视图,例如 DOM 操作修改。
注意:声明式开发并不是不进行 DOM 操作,而是把 DOM 操作频率降到最低。
“单项数据流” 概念解释
react 框架的原则中规定,子组件只可以使用父组件传递过来的 xxx 属性对应的值或方法,不可以改变。
数据只能单向发生传递(父传向子,不允许子直接修改父),若子组件想修改父组件中的数据,只能通过父组件暴露给子组件的函数(方法)来间接修改。
react 框架具体实现方式是设置父组件传递给子组件的”数据值或方法”仅仅为可读,但不可修改。
为什么要做这样的限制?
因为一个父组件可以有多个子组件,如果每个子组件都可修改父组件中的数据(子组件之间彼此共用父组件的数据),一个子组件的数据修改会造成其他子组件数据更改,最终会让整个组件数据变得非常复杂。
为了简化数据操作复杂程度,因此采用单向数据流策略,保证父组件数据的唯一最终可修改权归父组件所有。
“视图层渲染框架” 概念解释
react 框架自身定位是”视图层渲染框架”,单向数据流概念很好,但是实际项目中页面会很复杂。
例如顶级组件 Root 中分别使用了组件 A(由子组件 A0、A1、A2 构成)、组件 B(由子组件 A0、A1、A2 构成)、组件 C(由子组件 C0、C1、C2 构成),若此时组件 A 的子组件 A2 想和组件 C 的子组件 C1 进行数据交互,那么按照单向数据流的规范,数据操作流程为 A2 -> A -> Root -> C - C1,可以看出操作流程非常复杂。
**所以实际开发中,React 框架必须结合其他”数据层框架”(例如 Redux、Flux 等)**。
“函数式编程” 概念解释
react 自定义组件的各种交互都在内部定义不同的函数(js 语法规定:类 class 中定义的函数不需要在前面写 function 关键词),因此成为函数式编程。不像原生 JS 和 html 交互那样,更多侧重 html 标签、DOM 操作来实现视图和交互。
函数式编程的几点好处:
1、可以把复杂功能的处理函数拆分成多个细小的函数。
2、由于都是通过函数来进行视图层渲染和数据交互,更加方便编写”前端自动化测试”代码。
“虚拟 DOM” 概念解释
虚拟 DOM(Virtual Dom)就是一个 JS 对象(数组对象),用来描述真实 DOM。相对通过 html 标签创建的真实 DOM,虚拟 DOM 是保存在客户端内存里的一份 JS 表述 DOM 的数组对象。
用最简单的一个 div 标签来示意两者的差异,数据格式如下:
1 | //真实DOM数据格式(网页标签) |
虚拟 DOM 更新性能快的原因并不是因为在内存中(理论上任何软件都是运行在内存中),而是因为虚拟 DOM 储存的数据格式为 JS 对象,用 JS 来操作(生成/查询/对比/更新)JS 对象很容易。用 JS 操作(生成/查询/对比/更新)真实 DOM 则需要调用 Web Action 层的 API,性能相对就慢。
react 运行(更新)步骤,大致为:
1、定义组件数据 state
2、定义组件模板 JSX
3、数据与模板结合,生成一份虚拟 DOM
4、将虚拟 DOM 转化为真实 DOM
5、将得到的真实 DOM 挂载到 html 中(通过真实 DOM 操作),用来显示
6、监听 state 发生改变,若有改变重新执行第 3 步(数据与模板结合,生成另外一份新的虚拟 DOM)
7、在内存中对比前后两份虚拟 DOM,找出差异部分(diff 算法)
8、将差异部分转化为真实的 DOM
8、将差异化的真实 DOM,通过真实 DOM 操作进行更新
当 state 发生更改时,虚拟 DOM 减少了真实 DOM 的创建和对比次数(通过虚拟 DOM 而非真实 DOM),从而提高了性能。
“Diff 算法” 概念解释
当 state 发生改变时,需要重新生成新的虚拟 DOM,并且对旧的虚拟 DOM 进行差异化比对。
Diff 算法就是这个差异化比对的算法。
Diff 算法为了提高性能,优化算法,通常原则为:
同层(同级)虚拟 DOM 比对
先从两个虚拟 DOM(JS 对象)同层(即顶层)开始比对,如果发现同层就不一致,那么就直接放弃下一层(级别)的对比,采用最新的虚拟 DOM。
疑问点:假如两心虚拟 DOM 顶层不一致,但下一级别以及后面的更多级别都一致,如果仅仅因为顶层不一致而就该放弃下一级别,重新操作真实 DOM 从头渲染,岂不是性能浪费?
答:同层(同级)虚拟 DOM 比对,”比对”算法相对简单,比对速度快。如果采用多层(多级)比对,”比对”算法会相对复杂,比对速度慢。 同层虚拟 DOM 比对就是利用了比对速度快的优势来抵消”操作真实 DOM 操作性能上的浪费”。
列表元素使用 key 值进行比对
这里的 key 值是值”稳定的 key 值(是有规律的字符串,非数字)”,若 key 值为索引数字 index,那么顺序发生改变时,索引数字也会发生变化,无法判断之前的和现在的是否是同一个对象。
如果 key 值是稳定的,那么在比对的时候,比较容易比对出是否发生变化,以及具体的变化是什么。
Diff 算法还有非常多的其他性能优化算法,以上列出的”同层比对、key 值比对”仅仅为算法举例。
“高阶组件” 概念解释
高阶组件是一种组件设计方式(设计模式),就是将一个组件作为参数传递给一个函数,该函数接收参数(组件)后进行处理和装饰,并返回出一个新的组件。
简单来说就是,普通组件是根据参数(props)生成一个 UI(JSX 语法支持的标签)。而高阶组件是根据参数(组件)生成一个新的组件。
“生命周期函数” 概念解释
生命周期函数指在某一时刻组件会自动调用执行的函数。
这里的”某一时刻”可以是指组件初始化、挂载到虚拟 DOM、数据更改引发的更新(重新渲染)、从虚拟 DOM 卸载这 4 个阶段。
生命周期 4 个阶段和该阶段内的生命周期函数:
初始化(Initialization)
constructor()是 JS 中原生类的构造函数,理论上他不专属于组件的初始化,但是如果把它归类成组件组初始化也是可以接受的。
挂载(Mounting)
componentWillMount(即将被挂载)、render(挂载)、componentDidMount(挂载完成)
更新(Updation):
props 发生变化后对应的更新过程:componentWillReceiveProps(父组件发生数据更改,父组件的 render 重新被执行,子组件预测到可能会发生替换新数据)、shouldComponentUpdate(询问是否应该更新?返回 true 则更新、返回 flash 则不更新)、componentWillUpate(准备要开始更新)、render(更新)、componentDidUpdate(更新完成)
states 发生变化后对应的更新过程:shouldComponentUpdate(询问是否应该更新?返回 true 则更新、返回 flash 则不更新)、conponentWillUpdate(准备要开始更新)、、render(更新)、componentDidUpdate(更新完成)
props 和 states 发生变化后的更新过程,唯一差异是 props 多了一个 componentWillReceiveProps 生命周期函数。
componentWillReceiveProps 触发的条件是:
1、一个组件要从父组件接收参数,并且已存在父组件中(子组件第一次被创建时是不会执行 componentWillReceiveProps 的)
2、只要父组件的 render 函数重新被执行(父组件发生数据更改,子组件预测到可能会发生替换新数据),componentWillReceiveProps 就会被触发
捕获子组件错误:
componentDidCatch(捕获到子组件错误时被触发)
卸载(Unmounting):
componentWillUnmount(即将被卸载)
备注:自定义组件继承自 Component 组件,Component 组件内置了除 render()以外的所有生命周期函数。因此自定义组件 render()这个生命周期函数必须存在,其他的生命周期函数都可以忽略不写。
生命周期函数的几个应用场景:
1、只需要第一次获取数据的 Ajax 请求
如果组件有 ajax 请求(只需请求一次),那么最好把 ajax 请求写在 componentDidMount 中(只执行一次)。因为”初始化、挂载、卸载”在一个组件的整个生命周期中只会发生一次,而”更新”可以在生命周期中多次执行。
2、防止子组件不必要的重新渲染
若父组件发生 state 改变,那么会调用 render(),会重新渲染所有子组件。但是如果 state 改变的某个值与某子组件并不相关,如果此时也重新渲染该子组件会造成性能上的浪费。为了解决这个情况,可以在子组件中的 shouldComponentUpdate 生命周期函数中,做以下操作:
shouldComponentUpdate(nextProps,nextStates){
//判断xxx值是否相同,如果相同则不进行重新渲染
return (nextProps.xxx !== this.props.xxx); //注意是 !== 而不是 !=
}
对于类组件(由 class 创建的)和函数组件(由 function 创建的),他们对于生命周期的调用方法不同。
类组件:直接在类中定义函数;
函数组件:需要用到 hook(钩子),具体用法参见 Hook 用法;
React 中设置样式的几种形式
第一种:引用外部 css 样式
伪代码示例:
1 | import from './xxx.css'; |
注意:在 jsx 语法中,使用驼峰命名。例如原生 html 中的 classname 需要改成 className、background-color 要改成 backgroundColor。
第二种:内部样式
伪代码示例:
1 | return <div style={{backgroundColor:'green',width:'100px'}} /\> |
注意:内联样式值为一个对象,对象属性之间用”,”分割而不是原生 html 中的”;”。
因为是一个对象,因此下面代码也是可行的:
const mystyle = {backgroundColor:’green’,width:’100px’};
return <div style={mystyle} />
React 中数据传递的几种方式
在实际场景中,组件往往是由多级组件组合而成。组件之间数据传递(数据绑定)有多重形式,需要根据具体也许需求来选择使用哪种传递方式。
注:这里说的”数据传递”包含以下几层意思:
1、数据的获取
2、数据的修改(通过父级暴露给子组件函数来实现修改,这种方式也称为 “render props”)
3、根据数据变化重新渲染
以下文字描述中:
1、将顶级组件称之为”父组件”,用
2、将实际业务组件称之为”子组件”,用
3、将子组件中的子组件称之为”孙组件”,用
以上对组件的称呼仅仅是为了区别组件,事实上”子组件相对孙组件也可以称之为父组件”。
伪代码提示:
1、为了简化示例代码,省略了组件代码中 import 相关代码。
2、只演示数据向下传递,不演示修改上级数据
3、修改上级数据的方式是通过父组件定义修改数据的函数,并将该函数像数据一样传递给子组件或孙组件,子组件或孙组件通过调用该函数并传入修改值来实现上级数据修改。
第 1 种:默认设置属性传递
实现方式:父组件通过对子组件添加自定义属性和属性值来传递数据。
代码示例:
1 | //父组件给子组件添加属性num,值为2 |
若父组件要给孙组件传递数据,则每一层都需要进行接力传递。
1 | <Parent> |
优点:简单直接
缺点:需要层层传递,即使中间级别的组件不需要该数据,但是他也必须添加该属性,以保证数据能够接力向下传递。
第 2 种:使用组件组合传递
实现方式:若中间级别的组件不需要某属性,那么他可以采用{…}的形式将自身属性值传递给下一级组件中。
代码示例:
1 | <Parent> |
有点:中间级别的组件减少代码冗余(只是看上去减少了一些而已)
缺点:数据依然需要层层传递
第 3 种:使用 Context 传递
实现方式:
第 1 步:首先使用 React.createContext([defaultValue])来声明一个公共数据对象,例如 GlobalContext,可单独保存为 global-context.js。
说明如下:
1、可以声明多个不同的公共数据对象,并不要求必须全局唯一。
2、[defaultValue]为可选默认值,若父组件中未定义 value 属性值,则使用 defaultValue 作为默认要向下传递的数据值。
第 2 步:父级组件(顶级组件)中,进行以下操作:
1、引入该公共数据对象
2、添加静态对象 static contextType = GlobalContext
3、使用<GlobalContext.Provider value=’xxx’></GlobalContext.Provider>标签包裹要输出的组件代码,value=’xxx’就是定义要传递给子组件或孙组件的数据。
第 3 步:子组件或孙组件中,若不需要获取 GlobalContext 的值可以不做任何特殊处理,仅在需要获取 GlobalContext 的值的组件中,进行以下操作:
1、引入该公共数据对象
2、添加静态对象 static contextType = GlobalContext
3、使用<GlobalContext.Consumer value=’xxx’>{context => {//xxxxx }}</GlobalContext.Consumer>标签包裹要输出的组件代码,使用{this.context}来获取 GlobalContext 数据的值。
代码示例:
1 | //父组件给子组件或孙组件添加属性num,值为2 |
再次提醒:父组件使用<GlobalContext.Provider></GlobalContext.Provider>、子组件或孙组件使用<GlobalContext.Consumer></GlobalContext.Consumer>,且格式为<GlobalContext.Consumer>{context => { return xxxxx;}}</GlobalContext.Consumer>
第 4 种:使用第三方数据管理 Redux
实现方式:引入第三方该数据管理模块 redux。
Hook 用法
Hook 是 react16.8 以上版本才出现的新特性,可以在函数组件中使用组件生命周期函数,且颗粒度更加细致。
可以把 Hook 逻辑从组件中抽离出来,多个组件可以共享该 hook 逻辑。
请注意 hook 本质上是为了解决组件之间共享逻辑,并不是为了解决组件之间共享数据。
hook 表现出来特别像一个普通的 JS 函数(仅仅是表现出来但绝不是真的普通 JS 函数),如果加以扩展,是可以做到一定程度上组件之间共享数据的。
诞生 Hook 特性的背景原因:
第一个原因:在以前版本中,函数组件不支持组件生命周期函数,若想使用生命周期函数必须使用类组件,即由 class Xxx extends Component 这种形式来定义的组件。
第二个原因:类组件在使用过程中,常见有以下几个”痛点”:
1、函数中的 this 用法稍显复杂(js 中的 this 都很复杂),函数必须 bind(this)或者使用箭头函数。
2、类组件中生命周期函数固定,无法提供更细致的颗粒度。
举个例子:
假设你在 componentDidUpdate 函数中需要自定义 a,b,c 3 个变量,那么你必须把 a,b,c 3 件事情都写在 this.state = { a,b,c}中。
若你需要维护一个已经写好的组件,而该生命周期函数中存在 a,b 两个变量,那么你新增加 c 时,你还必须完整维护好 a,b 两个变量(添加 c 时千万别手滑修改了 a 或 b)。
我们更希望能够将 c 变量从 this.state 中分离出去,只去关心 c 就好,无需维护保持 a,b。因此就需要对生命周期函数有更加细致的颗粒度控制能力。
把上面这段话中的”变量”替换为”执行代码”也是合理的。
使用 Hook 以后,你可以单独把 c 从 state 中抽离出去、或者可以表达为:单独把 c 执行代码从某个生命周期函数中抽离出去。
3、类组件在个别情况下容易引发无意义的重新渲染。
4、类组件使用内部自定义数据稍显复杂(需要使用 this.state.xxx 这种形式)。使用 Hook 以后直接使用{xxxx}即可获取。
5、类组件中内部定义 state 数据会随着变化而变化,最初的 state 数据不会做备份保留。
6、若希望在类组件挂载后或数据更新后执行一些操作,那么你需要分别在 componentDidMount 和 componentDidUpdate 函数中分别写入相同的执行代码。使用 Hook 以后则 2 者合并为一个函数 useEffect 中。
7、类组件操作原生 DOM 不方便。使用 Hook 以后,就像普通 JS 函数操作原生 DOM 一样,非常简单。
基于以上原因,在 react 16.8 中引入了 Hook 这个概念。React 官方声明不会因为 Hook 的出现而取消或削弱类组件。
备注:像其他前端库,例如 Vue 也都有 Hook 用法。
Hook 的用法:
useState:用来在函数组件中自定义变量
使用方法:数组结构的语法 + useState(value)来实现组件内部自定义数据。
代码示例:
1 | import React,{useState} from 'react'; |
数组结构赋值的用法,在上面代码中 const [xxxx,setXxxx] = useState(xx)
等价于以下代码:
1 | const resArr = useState(xx); |
由此可见数组结构赋值可以使代码更加简洁。
useEffect:组件某些生命周期函数后执行额外的代码,也称为”副作用(额外的操作)”
“某些生命周期函数”是指这 3 个:componentDidMount、componentDidUpdate、componentWillUnmount
使用方法:直接在函数组件中使用 useEffect(() => {//…});,若该方法中有 return 返回函数,那么表示当组件生命周期函数执行完毕后,调用此 retrun 的返回函数。
代码示例:
1 | import React,{useState,useEffect} from 'react'; |
请注意,在 useEffect 中第 1 个参数()=>{},你可能会有疑惑,为什么不在其他地方单独定义一个函数,而是选择每次都返回一个不同的函数?
这是为了确保 effect 每次获取到的都是最新的值,而不用担心过期。 可以将每次新生成的()=>{}当成专门为此次渲染之后打造的执行函数。
若函数组件或自定义 Hook 中出现多个 useEffect,则会按照他们定义的先后顺序,依次执行。
使用 useEffect,会在组件渲染之后就执行一次。这里的”渲染之后”包括 3 个生命周期函数(componentDidMount、componentDidUpdate、componentWillUnmount)。
对于同一处的 useEffect,每次执行 useEffect 之前,会清理掉上一次执行的效果。
为什么要这样做?
为了确保代码的执行正确、一致性,为了避免可能因为忘记添加更新逻辑而出现的 bug。
该如何优化性能?
如果是类组件,可以在 componentDidUpdate 中添加 prevProps 或 prevState 值的对比,如果值未发生变化则通过 return false 来阻止重新渲染,以提高性能。
那么函数组件则可以通过给 useEffect()添加第 2 个参数(只有第 2 个参数发生变化时再会执行 useEffect 里的内容),以达到优化性能的目的。第 2 个参数通常被称为”依赖列表”。
代码示例:useEffect(()=>{//…},[xxxx]);
只有在 xxxx 发生真的变化后才会执行 useEffect 中第 1 个参数 ()=>{//…} 中的代码
如果你传入第 2 个参数是一个空数组[],那么表示该 useEffect 只会在第一次被挂载时执行一次。以后发生的 componentDidUpate、componentWillUnmount 生命周期函数时,均不会再次执行该 useEffect。
函数组件中定义(出现)多个 useState 和多个 useEffect,他们是如何对应(关联)的?
是按照他们定义(出现)的先后顺序进行 useState 和 useEffect 一一对应的。
代码示例:
1 | useState('Aa'); |
通过定义的先后顺序(成对出现),useEffect 会找到上一个最近定义的 useState,认为该 useState 是自己所需要关注的。当这个 useState 发生变化时则会执行自身(useEffect)。
这也是为什么不允许在 if/for/嵌套函数中使用 hook 的原因,因为那些语句本身包含有条件判断或逻辑,不能保证每一次执行的结果都是按照”固定顺序定义 hook”的,这样做很容易造成 bug。如果你这样做了,react 自带的 lint 插件会有错误警告提示。
如果就是需要用到 if 做一些判断来决定是否执行某些代码,可以将 if 放到 useEffect 中。这样是比较安全稳妥的做法。
useContext:获取 react 中自定义的共享数据对象 Context
若使用 函数组件 + Hook,不建议再使用 Context 来共享数据。Hook 已经可以让我们非常自由的去定义自己的数据(Hook 本身就像一个普通的 JS 函数)。
使用方法:const xxx = useContext(XxxContext)
代码示例:
1 | //假设我们有一个自定义GlobalContext,文件名为global-context.js |
以上为基础的 Hook 函数,以下为其他 Hook 函数
useReducer
useCallback
useMemo
useRef
useImperativeHandle
useLayoutEffect
useDebuValue
Hook 使用规则
1、Hook 只能在函数组件或自定义 Hook 中使用(类组件或者普通 JS 函数中均不可使用 Hook);
2、必须在函数内的最外层(不允许在 if/for/嵌套函数中使用)
3、使用次数不限(可多次使用),但是他们的定义先后顺序很重要。应该让 useEffect 紧随着需要关注的 useState;
4、可以从函数组件中抽离出来到一个单独的函数中,该函数通常称为”自定义 Hook”;
自定义 Hook
函数命名:约定自定义 Hook 函数名采用驼峰命名法,且必须以 use 开头,即 useXxxx;
补充说明:
1、react 自带 linter 插件来检测是否按照要求来命名,若不以 use 开头则会有错误警告。
2、实际中为了更加语义化,还可以命名为 useXxxStatus,表明该函数是用于处理 XxxStatus 状态的函数。
使用方法:将函数组件中 Hook 相关函数抽离出来,单独放在一个函数内,该函数即自定义 Hook。
代码示例:
1 | import React,{useState,useEffect} from 'react'; |
将 Hook 抽离出函数组件的一个好处是可以实现多个组件共用该 hook。