React 学习
React 简介
一、什么是 React ?(What is React?)
React是一个声明式的,高效的,并且灵活的用于构建用户界面的 JavaScript 库。它允许您使用”components(组件)“(小巧而独立的代码片段)组合出各种复杂的UI。
二、React谁开发的?
由Facebook开发且开源,近十年“陈酿”,阿里等大厂开始使用
三、为什么要学?
(一)原生js痛点
- 原生JavaScript操作DOM繁琐、效率低(DOM-API操作UI)
1
2
3
document.getElementById('app')
document.querySelector('#app')
document.getElementsByTagName('span')
使用JavaScript直接操作DOM,浏览器会进行大量的重绘重排
原生JavaScript没有组件化(模块化)编码方案,代码复用率低
(二)React特点
采用组件化模式,声明式编码,提高开发效率和组件复用率
在React Native中可以用React语法进行移动端开发
使用虚拟DOM和优秀的Diffing算法,尽量减少与真实DOM的交互,提高性能
四、React依赖介绍?
待更新~
React笔记
index.js 入口js: 用react-dom渲染注入点
1 |
|
Jsx是什么?
JSX是React.createElement的语法糖
jsx和React.createElement的互相转换
1 |
|
1 |
|
children位置的内容自动转义
1 |
|
循环渲染
数组里的jsx元素要有key属性,key值是能代表数据的唯一的字符串,通常来说key应该是数组元素的id值,如果没有这种数据用索引作为唯一值。
数组渲染时key的意义:
- 提高渲染效率
- key用index的缺陷:不能有效的更新jsx组件,index不得已的时候使用
代码碎片/代码片段组件: React.Fragment
目的:作为数组的上级容器组件,但不渲染额外dom元素
简写方式:<>...</>
自定义组件
命名:首字母大写的驼峰结构
函数组件:一个函数返回jsx对象就叫做函数组件
函数组件的属性就是函数的第一个参数,是object类型
父传子:通过属性
React的事件
React html组件上的dom定义的事件,回调函数里的event对象兼容dom的event
- 取得触发事件的元素:event.target
- 取得触发事件的名字:event.name
- 取消默认动作:event.preventDefault()
- 取消冒泡:event.stopPropagation()
状态 State (相对于Props)
props是一个js的object
state也是一个js的object
状态就是一种内部的属性 (不是通过父组件传给子组件的,是组件自己维护的属性)
父组件的state 传给子组件,就是子组件的属性
钩子函数 Hook 16.8
提供钩子函数使函数组件有各种其他的功能。
使用钩子函数的两个重点:
- 钩子函数只能使用在函数组件或者自定义的钩子函数里。
- 钩子函数只能在函数体的最外层生命周期里调用,不能在if/循环里调用。
对数组类型state的修改
正确处理方式:
将修改的内容放入一个新的数组 (对象),将它设置为下一次的状态,(当前状态 === 新的状态 是 false,就会进入重新渲染)
添加:
1 |
|
删除:
1 |
|
修改:
1 |
|
类组件和函数组件的区别
- 类组件有状态,函数组件无状态。
- 类组件有生命周期函数,函数组件无生命周期函数。
函数组件不是用来替换类组件的。
对状态值为数组类型的更新
增加
1 |
|
删除
1 |
|
修改
1 |
|
useEffect 函数的定义:
没有返回值
参数有2个:
第一个参数是一个回调函数,这个回调函数执行的内容叫做”副作用”,回调函数的返回值可以是空或者一个无参数的函数,无参数的函数叫做”消除副作用”
执行副作用的过程:挂载+更新后
消除副作用的过程:上次更新后的消除过程+卸载
函数组件的生命周期:
挂载:render,执行副作用
更新:render,消除上次副作用,执行副作用
卸载:消除上次副作用
第二个参数是指副作用的依赖关系(depends),数组或者空
- depends = 空,挂载和卸载一定会执行,每次render后会执行
- depends = [],只会挂载和卸载的时候执行,更新过程里不会执行
- depends = [props1,state1 …],挂载和卸载一定会执行,这次渲染的时候数组当中的变量和上次渲染的时候是不一样的,如果发生变化就会执行,没有发生变化就不会执行
类组件的生命周期
三个大的阶段:挂载,更新,卸载
挂载阶段
- constructor(props)
- 适合做初始化state和其他变量
- 不适合做发起异步网络请求,查询dom,其他副作用的过程,this.setState(constructor初始化state用直接赋值就可以了,不用setState)
- render() 渲染
- componentDidMount() 组件挂载完成
更新阶段 (0次或n次)
- shouldComponentUpdate (nextProps /* 下次属性 */, nextState/* 下次状态 */) 返回boolean值。
含义:要不要更新component?React.Component上的shouldComponentUpdate默认返回true
- componentDidUpdate(previousProps/* 上次的属性 */, previousState/* 上次的状态 */) 组件更新完成,dom元素渲染生效后 (previous上一次)
- 适合发起副作用;测量dom元素;允许执行this.setState,注意有条件的执行
this.forceUpdate() 强制刷新,略过shouldComponentUpdate
卸载阶段
- componentWillUnmount() 组件即将被卸载,组件一生中只会执行一次
- 适合做清理componentDidMount里开启的副作用
- 不适合发起副作用,或者this.setState
Ref
作用:用来获取渲染dom元素或者jsx元素,函数组件获取不到ref
使用场景
- 管理焦点,媒体播放,文本选择
- 尺寸的测量
- 强制触发动画
- 集成第三方 dom 库
React不能用dom获取元素,要使用ref
使用 Ref
### 函数组件里使用
useRef 钩子函数
1
2
3
4
5
6
7
8
9
10
import { useRef, useEffect } from 'react'
function MyComponent() {
const h = useRef() // {current: undefined}
const k = useRef()
useEffect(() => {
console.log(h)
}, [])
return <h1 ref={h}>hello</h1>
}
ref 属性不是 html 组件的属性,类似于 key 属性,属于 react 系统所需要的属性
useRef() 总返回同一个对象 `{current: null}`
### 类组件里使用
ref 对象用 React.createRef()获得
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React from 'react'
class MyComponent2 extends React.Component {
constructor(props) {
super(props)
this.input = React.createRef() // {current: undefined}
}
render() {
return (
<div>
<h1 ref={this.input}>你好</h1>
<button onClick={() => console.log(this.input)}>click</button>
</div>
)
}
}
### 回调函数方式
形式:`<组件 ref={(元素) => {v1 = 元素}}/>`
用途:
- 通用,函数组件和类组件都可以用
- 可以跟精细的处理ref属性
1
2
3
4
5
6
7
8
9
10
11
const MyComponent3 = () => {
let ref = null
return (
<>
<ul>
<li ref={(el)=>{ref=el}}>hello</li>
</ul>
<button onClick={() => console.log(ref)}>console.log(ref)</button>
</>
)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const students = [
{id:1,name: 'Alice'},
{id:2,name: 'Bob'},
{id:3,name: 'Carrey'}
]
const MyComponent4 = () => {
// let ref = [useRef(), useRef(), useRef()]
let ref = []
return (
<>
<ul>
{students.map((s, index) => <li key={s.id} ref={el => {
ref[index] = el
}}>{s.name}</li>)}
</ul>
<button onClick={() => console.log(ref)}>console.log(ref)</button>
</>
)
}
路由
原理
新浏览器:history对象管理的api (history.pushState/replaceState)
window.onpopstate - history记录前后移动的时候会触发这个事件
创建路由环境
浏览器环境里的路由器:BrowserRouter, HashRouter
import {BrowserRouter as Router} from ‘react-router-dom’
所有的路由都需要在路由环境里
配置路由
路由设置组件:Route, Switch, Redirect,Link,NavLink
Route组件
location 和 Route 规则如果匹配的上,就会渲染这个路由
属性:
- 和匹配有关的属性
- path 路径配置:必须从根目录开始(path=’/about’, path=’/account/passwd’, 错误path=’contact’)
- exact 精确匹配,默认exact=false
- strict 严格匹配,默认exact=false,精确匹配的优先级比严格匹配高
- sensitive 大小写匹配,默认sensitive=false,大小写匹配优先级比精确匹配高,sensitive > exact > strict
- 和渲染有关的属性
- render: 回调函数(匿名自定义组件),返回jsx对象,render只在路由匹配情况下运行回调函数
- children
- 回调函数,返回jsx对象, 不论路由是否匹配,函数都运行
- jsx元素
- component: 自定义组件(不是jsx元素)
Switch组件
作用:Switch组件的children是一组Route组件,按顺序,哪个路由组件先匹配到当前的location,哪个路由就渲染,其他的路由被忽略。
Redirect组件
用组件渲染的方式实现地址专向
- 路由属性
- exact 精确匹配
- strict 严格匹配
- sensitive 大小写匹配
Link组件
属性
- to
- 字符串: to=’/‘, to=’/blogs?limit=10#blog-1’
- location object,比字符串形式能够多传递state属性(历史记录的state)
- 函数:(location) => 返回新location对象,为了生成动态的地址
- replace 开关属性,replace默认值false,决定点击链接时是不是替换replace历史记录
- component用其他的组件渲染Link组件
NavLink组件 -导航链接
NavLink继承Link组件的所有属性
特殊属性:
- 路由上的匹配属性
- exact 精确匹配
- strict 严格匹配
- sensitive 大小写匹配
- 显示是否选中的属性
- className
- 除了字符串类型以为可以写回调函数(isActive/有没有匹配当前路径/)=>返回自己的class Name
- style
- (isActive)=>返回自己的style对象
- className
上下文 Context
用途
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
Context什么时候用?
- 共享全局状态
- 风格主题
- 应用设置
- 登录的用户信息
- 用户设置
- 多语言系统
- 一系列共享的服务
如何使用Context
第一步:创建Context
1 |
|
注意:默认值是在消费Context的时候,没有指定Context的上下文的时候才会有效。
第二步:提供Context容器
Context.Provider 组件内的子组件都属于这个上下文的环境。
1 |
|
第三步:消费Context的Value
接收Context的value
包含三种方式
- 设置class的static contextType属性
- 只能在类组件中使用
- 通过this.context访问对应context的值
- 特点:只能消费一个Context
1 |
|
- Context.Consumer 消费者标签
- 在类组件和函数组件中都可以使用
- Context.Consumer元素的children只能有一个,且是以context的值为参数的回调函数,该函数应该返回所渲染的JSX对象
- 特点:
- 通用性好;
- 支持多个上下文同时消费的能力;
- 只能在render函数中使用。
1 |
|
- useContext(Context)
- 只能在函数组件中使用
- useContext(Context)返回该context的值
- 特点:
- 支持多个上下文同时消费的能力;
- 使用灵活。
1 |
|
渲染原理
渲染并不是已经在dom上生成了html结构。渲染就是生成jsx对象(树)。
- 挂载的渲染一定会发生
- 重新渲染
- 以下发生改变的时候渲染(更新时的渲染)
- props
- state
- 消费的context
- 类组件执行this.forceUpdate()
- 父组件重新渲染的时候,所有后代节点都会重新渲染
- 以下发生改变的时候渲染(更新时的渲染)
类组件优化
通过继承React.PureComponent,可以优化组件,在自己的属性或state没有发生改变时,不会因为父组件重新渲染,而导致自身重新渲染。
1 |
|
原理:
普通的时候类组件继承自React.Component,shouldComponentUpdate函数永远都直接返回true(不做对比),每次都渲染。
如果使用React.PureComponent作为父类,shouldComponentUpdate函数里会根据props/state判断和下次的props/state是否发生改变,如果有改变才返回true,否则返回false,返回false的时候不会render。
函数组件优化
通过React.memo返回组件的高阶组件,该高阶组件在外部的props没有发生变化时不会重新渲染。
1 |
|
React.PureComponent 与 React.memo 功能对比
React.memo 返回值为高阶组件。它实现的效果与 React.PureComponent 相似,不同的是:
- React.memo 用于函数组件
- React.PureComponent 适用于 class 组件
- React.PureComponent 只是浅比较 props、state,React.memo 也是浅比较,但它可以自定义比较函数
为什么不是所有的组件都按照PureComponent的机制当成默认渲染逻辑?
因为比较属性或state也有性能消耗,有些时候有些组件并不需要特别比较属性或state,因为他们本来就是每时每刻都会渲染的,因此这种时候做这种对比是没有意义的,反而增加了运算负担。
useCallback/useMemo
为什么要缓存function或对象?
- 节省服务对复杂运算的损耗;
- 从缓存机制的考虑:当组件上的属性值是引用类型(往往是传递回调函数给属性),父组件重新渲染的时候,子组件的这个属性总是得到一个新的对象,使上面的这种对比的机制失效,每次对比完的结果都是需要重新渲染,使得上面(PureComponent或React.memo)优化没有作用,反而做了多余的运算。
解决办法:让这种属性在没有发生功能的变化的时候使用相同的值
useCallback的用法
- 参数:
- 函数
- 依赖关系数组:当依赖关系里的数据发生改变,会返回新的函数,如果依赖关系没变,得到上一次缓存的函数。
- 注意:useEffect使用undefined作为依赖关系,有意义;useCallback使用undefined/null作为依赖关系,没有意义。
- 返回值:函数
目的:组件属性得到的函数值,在不需要发生变化的时候,总得到同一个值,这样避免因为函数引用不同产生的多余的渲染
1 |
|
什么情况下使用useCallback
需要调用useCallback:
当传递属性的组件是PureComponent或者React.memo()的组件,回调方法需要用useCallback生成。
不需要调用useCallback:
- 组件结构简单,重新渲染的效能不会消耗过高,不用做缓存。
- html组件上的回调方法不用useCallback处理。
useMemo
- 参数:
- 回调函数: 没有参数,有返回值
- 依赖关系的数组
- 注意:useMemo的依赖关系是null/undefined,是没有意义的,因为这样每次render的时候,都会执行回调函数。
- 返回值
- 参数一:回调函数的执行结果
用途:
- 减少重复性的复杂运算
- 在依赖关系不变的情况下,得到相同的对象引用(跟useCallback得到不变的函数的理由类似)
1 |
|
useMemo和useCallback的差别
- useMemo返回的值:任何类型的。useCallback返回函数。
useCallback是useMemo的特殊情况:1
2
3
4const fn = useCallback((e) => { ... }, [])
const fn2 = useMemo(() => {
return (e) => { ... }
}, []) // 和useCallback效果一样 - useMemo的回调函数会直接执行,useCallback的回调函数不会直接执行(“直接”指调用useMemo或useCallback的时候)
- useMemo的回调函数没有参数,useCallback的回调函数可有参数
useReducer
作用:使用useReducer 替换 useState管理组件的状态。
reducer的概念来源
reducer 名字(概念)是来源于 Array.prototype.reduce函数。
1 |
|
什么是reducer
reducer是这样一类函数:
- 输入参数state和action,返回一个新的state
- reducer是纯函数
1 |
|
纯函数
- 不能运行副作用,比如网络请求、数据库查询
- 纯函数返回值应该完全依赖于它接收的参数,用相同的参数调用纯函数,每次得到的结果必定相同
- 纯函数的返回值不能有比如Math.random()或者Date.now()之类非纯函数参与
- 不要试图修改纯函数的参数,不要改变全局环境里的变量
使用
1 |
|
完整的函数形式
1 |
|
区分什么情况下用useState 和 useReducer
- useState:会影响state发生改变的条件是单一的。
- useReducer:
- 对一种数据操作有很多种不同的动作;
- 一个动作对多种数据都有影响;
- 状态结构比较复杂的时候用。
Redux
什么是Redux?
- 状态(State)的容器
- 状态是可以预测的。
什么是状态管理
- 状态存储
- 状态读取
- 状态变更
什么是数据的不可变性 Immutability (Immu tability )
Immutable Data 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。
对象修改
1 |
|
对象删除属性
1 |
|
数组添加数据
1 |
|
数组删除数据
1 |
|
数组修改数据
1 |
|
Redux三个原则(原理)
- 单一数据源(store):整个应用的state被存储在一棵object 树中,并且这个object树只存在于唯一一个store中。
- store上的state是只读:不能直接修改state,唯一修改state的方法是发送action给store(action是一个用于描述发生事件的普通对象)。
- 用纯函数来执行state的修改:为了实现action改变state,要编写reducer。
单向数据流(one-way data flow)
- 用state描述应用程序在特定时间点的状态
- 用state来渲染视图(View)
- 当发生事件(比如用户点击按钮),state会根据发生action进行更新,生成新的state
- 然后通知相关view重新渲染
state
- state是javascript的对象(object树)(数字,字符串,布尔值等等都可以成为state)
- 不能被序列化的数据不要存在state上(function)
action
- action是把数据从view传给store的有效荷载(payload)
- action是store上数据的唯一来源(dispatch(action))
- action是javascript对象,必须有type属性表示它执行的动作,除了此之外action其他属性由开发者自己决定(通常数据放在payload里)
action创建函数(action creator):返回action对象的函数
1 |
|
action 的作用只是描述发生事件的事实,它不包含描述如何更新state。
reducer
- reducer的概念来自于Array.prototype.reduce函数
- reducer是纯函数
- reducer处理action,和现有state一起,运算出新的state
- reducer处理多种action,对于它不能处理的action,直接返回state。
- reducer的形式
(state=初始值, action) => newState
,注意要提供state初始值
组合reducer(reducer可以互相调用)
数组里父级reducer调用子级reducer
1 |
|
合并多个reducer(可以在任何地方合并多次,不要认为只能在顶级reducer合并一次)
1 |
|
selector函数
和useSelector的参数有关
- selector是纯函数
- selector负责将store的state数据转换成渲染需要的数据结构
- selector函数的形式 (state) => 值
1 |
|
createStore方法
1 |
|
中间件 middleware
中间件:给dispatch增加额外功能,比如输出log,处理异步请求
1 |
|
redux-thunk
thunk:函数实现“传名调用”,用函数返回另外一个函数,固定一部分参数,返回临时函数就叫作thunk函数。
redux-thunk 库让dispatch(本来dispatch只能发送action,也就是普通的object)可以发送函数作为参数 (dispatch, getState) => {}
thunk action creator的形式:
1 |
|
RTK
创建项目
1 |
|
如果已有的react项目
1 |
|
slice 切片
利用createSlice,集中创建了跟一种应用相关数据的reducer, selectors和actions。
features 功能
RTK中推荐把和应用有关的数据、组件都放在一个文件结构下,这些模块共同形成的应用功能称为feature。
configureStore
替代createStore函数,使用比以前简单,自动集成了thunk中间件和开发工具。
1 |
|
configureStore自定义middleware的方式:
1 |
|
createSlice
通过slice对象创建reducer和actions,并且可以处理外部的actions。
示例
1 |
|
createAsyncThunk
生成一个异步的action,会自动发送pending/fulfilled/rejected的action
1 |
|
RTK对Redux使用的优化
- 用configureStore替代createStore方法,使创建store的过程更加简单,自动集成了必要的中间件(thunk和devtools)
- 创建slice时必须给配置默认状态,避免了手工创建reducer的时候忘记给state默认值的情况。
- 使用slice创建reducer的同时创建相关的action,减少手动创建action creator的模版代码,使用reducers对象替代switch语句。
- 使用immer库,使reducer里可以用非immutable的风格书写纯函数,使得修改数据的书写效率和程序结构都得到大幅度的改善。
- 使用createAsyncThunk函数创建异步方法,减少手动创建thunk action的复杂度。