status
Published
slug
react-learning-note
type
Post
category
Technology
date
Oct 20, 2022 → Feb 24, 2023
tags
React
框架
前端
JavaScript
summary
学习笔记内容,来源文档、教程、视频,仅作为个人学习记录
由于实验室的项目需要,上学期开始学习React,通过教程实例编程、读文档、看视频教程等方式对React相关的知识进行了系统性的了解,学习笔记内容如下,结构较为松散,仅作为个人学习的笔记记录。
MDN-Getting started with ReactReact基础学习副效应React HooksReact 默认提供的四个最常用的钩子Ref转发:React中的函数ContextReact教程React相关知识AntDesign的使用:Redux:React的扩展知识React Router 6
MDN-Getting started with React
按照该教程入门,尝试编写了一个使用React实现的ToDo列表,熟悉了一下React中的JSX写法,组件概念(这里和Vue感觉是一致的),子组件和父组件的信息传递等内容,通过一步步的优化和改动,逐步加深理解。
一点点笔记
useState:返回一个 state,以及更新 state 的函数
useRef:useRef 返回一个可变的 ref 对象
useEffect:让函数型组件拥有处理副作用的能力,类似生命周期函数。
usePrevious写法:
React基础学习
副效应
函数式编程将那些跟数据计算无关的操作,都称为 "副效应" (side effect)。如果函数内部直接包含产生副效应的操作,就不再是纯函数了,我们称之为不纯的函数。
React Hooks
React函数式编程
React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。React Hooks 就是那些钩子。
钩子(hook)就是 React 函数组件的副效应解决方案,用来为函数组件引入副效应。函数组件的主体只应该用来返回组件的 HTML 代码,所有的其他操作(副效应)都必须通过钩子引入。
React 默认提供的四个最常用的钩子
- useState():状态钩子。
useState()
接受状态的初始值,作为参数,- 该函数返回一个数组,数组的第一个成员是一个变量,指向状态的当前值。第二个成员是一个函数,用来更新状态,约定是
set
前缀加上状态的变量名。
- useContext():共享状态钩子
useContext()
钩子函数用来引入 Context 对象- 可以深层组件传值,父组件传给子孙组件。接收一个 context 对象(
React.createContext
的返回值)并返回该 context 的当前值。 - 使用方法:使用
Context
,首先顶层先声明Provier
组件,并声明value
属性,接着在后代组件中声明Consumer
组件,这个Consumer
子组件,只能是唯一的一个函数,函数参数即是Context
的负载。如果有多个Context
,Provider
和Consumer
任意的顺序嵌套即可。
- useReducer():action 钩子
useReducer()
的基本用法,接受 Reducer 函数和状态的初始值作为参数,返回一个数组。数组的第一个成员是状态的当前值,第二个成员是发送 action 的dispatch
函数。
- useEffect():副作用钩子
useEffect()
用来引入具有副作用的操作,最常见的就是向服务器请求数据- 传给
useEffect
的函数会在浏览器完成布局与绘制之后,在一个延迟事件中被调用。 useEffect
传递第二个参数时,该值改动才重新渲染- 想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组(
[]
)作为第二个参数。
Hooks 代码还可以封装起来,变成自定义的 Hook
其他的钩子函数:
- useRef():获取组件实例对象或者是DOM对象
useRef(initialValue)
接受一个参数(引用的初始值)并返回一个可变的引用对象(也称为ref
)。- 引用只是一个具有特殊属性
current
的对象:ref.current .current
属性被初始化为传入的参数(initialValue
),返回的ref对象在组件的整个生命周期内持续存在reference.current
访问引用,并且reference.current = newValue
更新引用值:
useState()和useRef()的区别:
- 更新 state 会触发组件重新呈现,而更新 ref 则不会。
- state 更新是异步的(state变量在重新呈现后更新),而ref则同步更新(更新后的值立即可用)
Ref转发:
Ref 转发是一项将 ref 自动地通过组件传递到其一子组件的技巧
React.forwardRef
接受一个渲染函数,其接收props
和ref
参数并返回一个 React 节点。
React中的函数
组件实例被创建并插入DOM中时的生命周期调用顺序:
- constructor()
- static getDerivedStateFromProps()
- render()
- componentDidMount():在组件挂载后(插入 DOM 树中)立即调用
当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:
- static getDerivedStateFromProps()
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate()
- componentDidUpdate()
当组件从 DOM 中移除时会调用如下方法:
- componentWillUnmount():在组件卸载及销毁之前直接调用
当渲染过程,生命周期,或子组件的构造函数中抛出错误时,会调用如下方法:
- static getDerivedStateFromError()
- componentDidCatch()
记忆化函数:
- useCallback():返回一个记忆化的回调函数。 将记忆化视为缓存一个值,以便不需要重新计算。 这使我们能够隔离资源密集型函数,以便它们不会在每次渲染时自动运行。
- 防止组件重新渲染,除非其 props 已更改。
- useMemo():传递一个创建函数和依赖项,创建函数会需要返回一个值,只有在依赖项发生改变的时候,才会重新调用此函数,返回一个新的值。
useReducer:useReducer 是 useState 的替代方案,用法:
const [state, dispatch] = useReducer(reducer, initialArg, init);
使用dispatch的参数是reducer的action,解耦了操作逻辑(action)和后续的行为(一般是 UI 的更新), reducer 函数根据传入的 action 执行某些逻辑,最后返回的就是新状态。
Context
为组件提供上下文,传递参数
context.provider:Provider 接收一个
value
属性,传递给消费组件。一个 Provider 可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据。- 当 Provider 的
value
值发生变化时,它内部的所有消费组件都会重新渲染。
context.consumer:一个 React 组件可以订阅 context 的变更,此组件可以让你在函数式组件
中可以订阅 context。
useImperativeHandle
可以让你在使用ref
时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle(ref, createHandle, [deps])
React教程
React只关注界面
React相关知识
为什么学React?
- 原生JavaScript操作DOM繁琐、效率低(DOM-API操作UI)
- 使用JavaScript直接操作DOM,浏览器会进行大量的重绘重排
- 原生JavaScript没有组件化编码方案,代码复用率低
React的特点
- 采用组件化模式、声明式编码,提高开发效率及组件复用率
- 在React Native中可以使用React语法进行移动端开发
- 使用虚拟DOM+优秀的Diffing算法,尽量减少与真实DOM的交互
- DOM Diffing:渲染时进行虚拟DOM的比较,之前存储下来的DOM内容不重新生成
为什么使用JSX?+语法规则
JS生成虚拟DOM的时候十分繁琐,需要使用React.CreateElement()创建
JSX可以看做是JS的语法糖。
JSX的语法规则:
- 定义虚拟DOM时,不要写引号
- 标签中混入JS表达式时要用{}
- 样式的类名指定不要用class,要用className
- 内联样式,要用style={key:value}的形式去写
- 虚拟DOM必须只有一个根标签
- 标签必须闭合
- 标签首字母
- 若小写字母开头,则将该标签转为html中同名元素,若html中无标签对应的同名元素,则报错
- 若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。
虚拟DOM:
- 本质是Object类型的对象(一般对象)
- 虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在用,无需真实DOM上那么多属性
- 虚拟DOM最终会被React转化为真实DOM,呈现在页面上
区分js语句和js表达式
- 表达式:一个表达式产生一个值,可以放在任何一个需要值的地方
- 语句:js代码,for语句,switch语句,if语句
模块与组件
模块:向外提供特定功能的js程序,一般就是一个js文件
- 为什么要拆为模块:随着业务逻辑增加,代码越来越多且复杂
- 作用:复用js,提高js的编写,提高js的运行效率
组件:用来实现局部功能想过的代码和资源(html/css/js/image)的集合
- 为什么需要组件:一个界面的内容更复杂
- 作用:复用编码,简化项目编码,提高运行效率
组件(函数式组件和类式组件)
函数式组件:函数名即为组件名
类式组件:
- 必须继承React.Component,必须使用render()
- 类中的方法默认开启局部的严格模式
constructor(构造器)和render()方法的this都是当前类的实例对象
- 其他类中的this,若要使用为实例对象可以在构造器中添加如下代码
- 可以进行状态初始化
组件实例【类式组件才能谈实例】的三大核心属性:
1. State
- 是组件对象最重要的属性,必须为一个对象
- 状态不可直接更改,必须要使用内置API进行更改
- state的更新是一种合并,不是替换
- 组件被称为“状态机”,通过更新组件的state来更新对应的页面显示(重新渲染)
- 每一次调用setState进行的state更新,都会触发组件执行render()函数
- state的初始化
在类的构造器中初始化,代码比较繁琐- 直接在类中初始化即可
- 组件自定义方法中的this为undefined,如何解决?
- 强制绑定this到自定义方法,使用函数对象的bind()
- 自定义方法要用赋值语句的形式+箭头函数
- 箭头函数的this会取离得最近的this
2. Props
- 可以从组件外部传递值到组件内部
- 对标签属性进行类型、必要性的限制
- 在属性propTypes中进行相应的设置
- 指定默认标签属性值
- 函数式组件使用props
3. Refs
- String类型的Refs被弃用了:效率不高
- 通过在组件中使用字符串指明refs,React自动收集ref到this.refs中
- 回调函数形式的ref
- 若使用箭头函数,每次渲染时,回调函数形式的ref都会执行两次
- 修改方法:将ref的回调函数定义成class的绑定函数
- createRef API实现(最为推荐的一种方式)
- React.creatRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是专人专用的
React中的事件处理
- 事件处理名的格式:onXxxx
- React使用的是自定义(合成)事件,而不是使用的原生DOM事件 —— 为了更好的兼容性
- React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)—— 为了更高效
- 通过event.target得到发生事件的DOM元素对象 —— 不要过度使用ref
React中收集表单数据
阻止表单提交(阻止默认函数的调用)
event.preventDefault
非受控组件:现用现取
受控组件:所有的属性维护在状态中,使用onChange
高阶函数_函数柯里化
高阶函数:如果一个函数符合下面2个规范中的任何一个,该函数就是高阶函数
- 若A函数,接收的参数是一个函数,那么A级就可以称之为高阶函数
- 若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数
常见的高阶函数有:Promise,setTimeout,arr.map()等等
函数柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
保存表单数据到状态中
为什么使用
[]
[xxx]可以看成是从对象中取出xxx属性
不使用函数柯里化的写法
组件的生命周期
旧生命周期
- shouldComponentUpdate():控制组件更新的“阀门”
- 默认返回true,自定义必须返回bool值
- 注意两条路线:setState()、forceUpdate()
- componentWillReceiveProps():第一次调用的时候不执行(不算)
- 生命周期总结:
- 初始化阶段:由ReactDOM.render()触发——初次渲染
- constructor()
- componentWillMount()
- render()
- componentDidMount()
- 一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
- 更新阶段:由组件内部this.setState()或父组件render触发
- shouldComponentUpdate()
- componentWillUpdate()
- render()
- componentDidUpdate()
- 卸载组件:由ReactDOM.unmountComponentAtNode()触发
- componentWillUnmount()
- 一般在这个钩子中做一些收尾的事,例如:关闭定时器,取消订阅消息
即将废弃的钩子,下一个大版本使用需要加上UNSAFE_前缀
componentWillMount()
componentWillReceiveProps()
componentWillUpdate()
新生命周期
- getDerivedStateFromProps():
- getDerivedStateFromProps返回的值会改变state的值,且被修改后的值不能再被setState()修改
- 特殊用例,state的值取决于props
- getSnapshotBeforeUpdate():
- 在最近一次渲染输出之前调用,是的组件能在更新之前从DOM中捕获一些信息
- 在更新之前获取快照,返回值会作为参数传给componentDidUpate()方法
- 用法:以特殊方式处理滚动条位置
DOM的diffing算法
diffing算法的最小粒度是标签。
经典面试题:
- react/vue中的key有什么用?(key的内部原理是什么)
- 为什么遍历列表时,key最好不要用index?
- 虚拟DOM中key的作用
- 简单来说:key是虚拟DOM对象的标识,在更新显示时key起极重要的作用
- 详细来说:当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
- 旧虚拟DOM中找到了与新虚拟DOM相同的key:
- 若虚拟DOM中内容没变,直接使用之前的真实DOM
- 若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
- 旧虚拟DOM中未找到与新虚拟DOM相同的key
- 根据数据创建新的真实DOM,随后渲染到页面
- 使用index作为key可能会引发的问题:
- 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
- 会产生没有必要的真实DOM更新 ⇒ 界面效果没有影响,但会降低运行效率
- 如果结构中还包含输入类的DOM:
- 会产生错误DOM更新 ⇒ 界面有问题,导致子DOM不重新渲染,信息发生错乱
- 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序的操作,仅用于渲染列表,用于展示,使用index作为key是没有问题的
⇒ 不推荐使用index(索引值)作为key
- 开发中如何选择key?
- 最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值
- 如果确定只是简单的展示数据,用index也是可以的
React脚手架:用于帮助程序员快速创建一个基于React库的模板项目
- 基于webpack
- React在页面中引入的为App组件,其他自定义的组件都应该在App中
- 脚手架中包裹<App/>的<React.StrictMode>可以检查子组件中 不合理的内容
- 组件文件后缀推荐使用.jsx
- 组件中文件夹中使用index.css,index.jsx,引用时可以只写到文件夹目录
- 样式的模块化:使用index.module.css(加上module)
React实例:ToDoList
- 拆分组件、实现静态组件,注意:className、style的写法
- 动态初始化列表,如何确定将数据放在哪个组件的state中
- 某个组件使用:放在自身的state中
- 某些组件使用:放在他们共同的父组件state中(官方称呼:状态提升)
- 关于父子组件之间的通信
- 父组件给子组件传递数据:通过props传递
- 子组件给父组件传递数据:通过props传递,要求父提前给子传递一个函数
- 注意defaultChecked和checked的区别,类似的还有:defaultValue和value
- 状态在哪里,操作状态的方法就在哪里
React ajax
- 常用的ajax请求库
- axios(推荐使用):轻量级
- 封装XmlHttpRequest对象的ajax
- promise风格
- 可以用在浏览器端和node服务器端
- jQuery:比较重
连续解构赋值
兄弟组件之间沟通:消息订阅+发布机制
- 订阅+发布机制实际上适用任何组件之间的消息传递
工具库:PubSubJS
订阅消息:
- 消息名(确定)
- 发布消息
Fetch发送请求
- Fetch关注分离的设计思想
React路由 【p127 为最新版本】
- SPA应用(Single Page Web Application)单页面应用
- 局部更新
- 数据通过ajax请求,在前端异步展现
- 路由
- 前端路由的实现:基于history(BOM [浏览器对象] 的history,前端路由的基石)实现
- 浏览器的历史记录是一个栈
- react-router-dom库(react-router的web实现)
- BrowserRouter
- HashRouter:多一个#号,#号后的都不会作为资源传给服务器
- 路由组件和一般组件的不同
- 写法不同
- 一般组件:
<Demo/>
- 路由组件:
<Route path='demo' component={Demo}/>
- 存储位置不同
- 一般组件:components
- 路由组件:pages
- 接收到的props不同
- 一般组件:写组件标签时传递了什么,就能收什么
- 路由组件:接收到三个固定的属性
- go()
- goBack()
- goForward()
- push()
- replace()
- pathname
- search
- state
- params
- path
- url
history:
location:
match:
<NavLink>
- 区别于
<Link>
会给组件添加activeClassName类名,默认active
- 标签体内容是一个特殊的标签体属性
- 即this.props.children
<Switch>
- 路由在匹配的时候,会一直往下匹配
- 使用
<Switch>
包裹路由,可以实现匹配上就不再往下匹配,代码效率更高
解决多级路径刷新页面样式丢失的问题
- 路由多层级结构,刷新后导致css的路径错误
- 解决方案:
<BrowserRouter>
改用<HashRouter>
- 资源路径使用
%PUBLIC_URL%
- 资源路径的开头去掉
.
路由的模糊匹配:默认使用,输入的路径必须包含匹配的路径,且顺序要一致
- 路由的路径匹配:最左前缀匹配
- 使用exact关键字,表示严格匹配:
<Route exact path=’/home’ component={Home}/>
或者<Route exact=true path=’/home’ component={Home}/>
- exact严格匹配使用原则:只有不使用会影响页面正常展示时才使用
Redirect的使用
- 一般写在所有路由注册的下方,当所有路由都没有匹配的时候,跳转到Redirect指定的路由
嵌套路由(二级路由)
- 在父组件(被嵌套)中继续注册路由
- 注册子路由(嵌套组件)的路径需要带上父路由的路径值
- 路由的匹配是按照注册路由的顺序进行的
向路由组件传递参数
params参数:
search参数:获取到的search是urlencoded编码字符串,需要借助querystring解析
state参数:刷新也可以保留住参数,不会在地址栏显示
路由跳转的两种模式
- push模式(默认模式,类似于压栈,留下痕迹)
- replace模式(替换栈顶,不留下痕迹)
编程式路由导航:借助this.props.history上的API对路由进行操作
一般组件中操作路由:使用withRouter
- withRouter可以加工一般组件,让一般组件具备路由组件所特有的API
- withRouter的返回值是一个新组件
BrowserRouter和HashRouter的区别
- 底层原理不一样:
- BrowserRouter使用的是H5的history API,不兼容IE9及以下版本
- HashRouter使用的是URL的哈希值
- path表现形式不一样
- BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
- HashRouter的路径包含#,例如:localhost:3000/#/demo/test
- 刷新后对路由state参数的影响不一样
- BrowserRouter没有任何影响,因为state保存在history中
- HashRouter刷新后会导致路由state参数的丢失
- 备注:HashRouter可以用于解决一些路径错误相关的问题
AntDesign的使用:
- 基本使用
- 按需引入+自定义主题
Redux:
React 的状态管理工具,不一定必须,Redux 的适用场景:多交互、多数据源
Redux
- 专门用于状态管理的JS库
- 可以用在react、angular、vue等
- 作用:集中式管理React应用中多个组件共享的状态
- 使用情况:
- 某个组件的状态需要让其他组件可以随时拿到(共享)
- 一个组件需要改变另一个组件的状态(通信)
- 使用原则:能不用就不用,如果不用比较吃力才考虑使用
- 原理图
- 三个核心概念
- action
- type
- data
- reducer
- store
- 同步action&异步action
- 同步action指action的值为Object类型的一般对象
- 异步action指action的值为函数
- 默认action类型为Object,若要使用函数类型的action需要使用中间件(redux-thunk)
- 函数中执行异步任务,异步流程结束后会分发一个同步的action去真正操作数据
- 异步action不是必须要写的,完全可以自己等待异步任务的结果再去分发同步action
react-redux
- facebook的插件库
- react-redux模型图
- 容器组件:使用connect连接
- 备注:容器组件中的store是靠props传进去的,而不是在容器组件中直接引入
- mapDispatchToProps可以传递的参数
- 函数
- 对象
- connect中隐含监测逻辑,使用后不再需要使用
store.subscribe()
自行监测+重新渲染
- 数据共享
- combineReducers
React的扩展知识
setState()
- setState()的更新是异步的,不能够直接获取,若要使用最新的状态值,有两种更新状态的写法
- 对象式的setState
- stateChange为状态改变对象
- callback是可选的回调函数,在状态更新完毕,页面也更新后(render调用后)才被调用
- 函数式的setState
- updater为返回stateChange对象的函数
- updater可以接收到state和props
- callback是可选的回调函数,在状态更新,页面也更新后(render调用后)才被调用
- 对象式的setState是函数式setState的简写方式(语法糖)
- 使用原则:
- 如果新状态不依赖于原状态 ⇒ 使用对象方式
- 如果新状态依赖于原状态 ⇒ 使用函数方式
- 如果需要在setState()执行后获取最新的状态数据,要在第二个callback函数中读取
lazyLoad
- 懒加载,使用时才加载组件,有助于提升性能
- 组件调用Lazy
- 注册路由使用Suspense包裹
state Hook
- useState:状态钩子。
useState()
接受状态的初始值,作为参数,- 该函数返回一个数组,数组的第一个成员是一个变量,指向状态的当前值。第二个成员是一个函数,用来更新状态,约定是
set
前缀加上状态的变量名。 - React底层进行了缓存,第一次调用的后,useState不会改变状态值
Effect Hook
- useEffect:副作用钩子,监测作用,可以在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
- React中的副作用操作:
- 发ajax请求数据获取
- 设置订阅/启动定时器
- 手动更改真实DOM
- 语法和说明
- useEffect()可以类比成componentDidMount、componentDidUpdate、componentWillUnmount三个函数的组合
Ref Hook
- useRef():可以在函数组件中存储/查找组件内的标签或任意其他数据
- 语法:
作用:保存标签对象,功能与React.createRef()一样
Fragment
- 文档碎片,在解析时会被去除,只能传key这个参数
Context
- 一种组件间的通信方式,常用于【祖组件】和【后代组件】间通信
- 使用:在应用开发中一般不用context,一般都用它的封装react插件
Component相关优化
- Component的两个问题
- 只要执行setState(),即便不改变状态数据,组件也会重新render() ⇒ 效率低
- 只要当前组件重新render(),自动重新render()子组件,纵使子组件没有用到父组件的任何数据 ⇒ 效率低
- 原因:Component中的shouldComponentUpdate()总是返回true
- 解决方法:
- 方法一:重写shouldComponentUpdate()方法,比较新旧state或props数据,有变化才返回true,没有变化返回false
- 方法二:使用PureComponent
- PureComponent重写了shouldComponentUpdate(),只有state或props数据有变化才返回true
- 注意:
- 只是进行state和props数据的浅比较,如果只是数据对象内部数据变了,返回false
- 不要直接修改state数据,而是要产生新数据
- 项目中一般使用Purecomponent来优化
⇒ 提高效率的方法:只有当组件的state或props数据发生改变时才render()
Render Props
- 如何向组件内部动态传入带内容的结构(标签)
- 使用children props:通过组件标签传入结构
- 使用render props:通过组件标签属性传入结构,一般用render函数属性
- chidren props“:件之间的内容为特殊的props属性——children
- render props:
Error Boundary(错误边界)
用来捕获后代组件错误,渲染出备用页面
- 控制错误在一定范围内,不让子组件的错误影响到其他组件
- 错误边界仅适用于开发环境
- 特点:只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
- 使用方法:getDerivedStateFromError配合componentDidCatch
组件间通信方式总结
- 组件间的关系:
- 父子组件
- 兄弟组件
- 祖孙组件
- 几种通信方式:()中是推荐的搭配方式
- props:(父子组件)
- children props
- render props
- 消息订阅-发布:(兄弟、祖孙组件)
- pubs-sub,event等等
- 集中式管理:(兄弟、祖孙组件)
- redux、dva等等
- conText:(祖孙组件)
- 生产者-消费者模式
React Router 6
- 变化
- <Outlet>指定路由组件展示的位置
- Props参数:useParams
- Search参数:useSearchParams
- State参数:useLocation
- 可以用来判断当前组件有无挂载嵌套路由