react学习二
React 入门学习(十四)– redux 基本使用
引言
在了解了 Antd 组件库之后,我们现在开始学习了 Redux ,在我们之前写的案例当中,例如:todolist 案例,GitHub 搜索案例当中,我们对于状态的管理,都是通过 state 来实现的,比如,我们在给兄弟组件传递数据时,需要先将数据传递给父组件,再由父组件转发 给它的子组件。这个过程十分的复杂,后来我们又学习了消息的发布订阅,我们通过 pubsub 库,实现了消息的转发,直接将数据发布,由兄弟组件订阅,实现了兄弟组件间的数据传递。但是,随着我们的需求不断地提升,我们需要进行更加复杂的数据传递,更多层次的数据交换。因此我们为何不可以将所有的数据交给一个中转站,这个中转站独立于所有的组件之外,由这个中转站来进行数据的分发,这样不管哪个组件需要数据,我们都可以很轻易的给他派发。
而有这么一个库就可以帮助我们来实现,那就是 Redux ,它可以帮助我们实现集中式状态管理
1. 什么情况使用 Redux ?
首先,我们先明晰 Redux
的作用 ,实现集中式状态管理。
Redux
适用于多交互、多数据源的场景。简单理解就是复杂
从组件角度去考虑的话,当我们有以下的应用场景时,我们可以尝试采用 Redux
来实现
- 某个组件的状态需要共享时
- 一个组件需要改变其他组件的状态时
- 一个组件需要改变全局的状态时
除此之外,还有很多情况都需要使用 Redux 来实现(还没有学 hook,或许还有更好的方法)
(从掘友的文章里截的图)
这张图,非常形象的将纯 React 和 采用 Redux 的区别体现了出来
2. Redux 的工作流程
首先组件会在 Redux 中派发一个 action
方法,通过调用 store.dispatch
方法,将 action
对象派发给 store
,当 store
接收到 action
对象时,会将先前的 state
与传来的 action
一同发送给 reducer
,reducer
在接收到数据后,进行数据的更改,返回一个新的状态给 store
,最后由 store
更改 state
(图来自掘金社区,侵删)
3. Redux 三个核心概念
1. store
store
是 Redux 的核心,可以理解为是 Redux 的数据中台,我们可以将任何我们想要存放的数据放在 store
中,在我们需要使用这些数据时,我们可以从中取出相应的数据。因此我们需要先创建一个 store
,在 Redux 中可以使用 createStore
API 来创建一个 store
在生产中,我们需要在 src
目录下的 redux
文件夹中新增一个 store.js
文件,在这个文件中,创建一个 store
对象,并暴露它
因此我们需要从 redux
中暴露两个方法
import {
createStore,
applyMiddleware
} from 'redux'
并引入为 count 组件服务的 reducer
import countReducer from './count_reducer'
最后调用 createStore
方法来暴露 store
export default createStore(countReducer, applyMiddleware(thunk))
这里采用了中间件,本文应该不会写到~
在 store
对象下有一些常用的内置方法
获取当前时刻的 store
,我们可以采用 getStore
方法
const state = store.getState();
在前面我们的流程图中,我们需要通过 store
中的 dispatch
方法来派生一个 action
对象给 store
store.dispatch(`action对象`)
最后还有一个 subscribe
方法,这个方法可以帮助我们订阅 store
的改变,只要 store
发生改变,这个方法的回调就会执行
为了监听数据的更新,我们可以将 subscribe
方法绑定在组件挂载完毕生命周期函数上,但是这样,当我们的组件数量很多时,会比较的麻烦,因此我们可以直接将 subscribe
函数用来监听整个 App
组件的变化
store.subscribe(() => {
ReactDOM.render( < App /> , document.getElementById('root'))
})
2. action
action
是 store
中唯一的数据来源,一般来说,我们会通过调用 store.dispatch
将 action 传到 store
我们需要传递的 action
是一个对象,它必须要有一个 type
值
例如,这里我们暴露了一个用于返回一个 action
对象的方法
export const createIncrementAction = data => ({
type: INCREMENT,
data
})
我们调用它时,会返回一个 action
对象
3. reducer
在 Reducer 中,我们需要指定状态的操作类型,要做怎样的数据更新,因此这个类型是必要的。
reducer 会根据 action 的指示,对 state 进行对应的操作,然后返回操作后的 state
如下,我们对接收的 action 中传来的 type 进行判断
export default function countReducer(preState = initState, action) {
const {
type,
data
} = action;
switch (type) {
case INCREMENT:
return preState + data
case DECREMENT:
return preState - data
default:
return preState
}
}
更改数据,返回新的状态
4. 创建 constant 文件
在我们正常的编码中,有可能会出现拼写错误的情况,但是我们会发现,拼写错误了不一定会报错,因此就会比较难搞。
我们可以在 redux
目录下,创建一个 constant
文件,这个文件用于定义我们代码中常用的一些变量,例如
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
将这两个单词写在 constant
文件中,并对外暴露,当我们需要使用时,我们可以引入这个文件,并直接使用它的名称即可
直接使用 INCREMENT
即可
5. 实现异步 action
一开始,我们直接调用一个异步函数,这虽然没有什么问题,但是难道 redux 就不可以实现了吗?
incrementAsync = () => {
const { value } = this.selectNumber
const { count } = this.state;
setTimeout(() => {
this.setState({ count: count + value * 1 })
}, 500);
}
我们可以先尝试将它封装到 action
对象中调用
export const createIncrementAsyncAction = (data, time) => {
// 无需引入 store ,在调用的时候是由 store 调用的
return (dispatch) => {
setTimeout(() => {
dispatch(createIncrementAction(data))
}, time)
}
}
当我们点击异步加操作时,我们会调用这个函数,在这个函数里接收一个延时加的时间,还有action所需的数据,和原先的区别只在于返回的时一个定时器函数
但是如果仅仅这样,很显然是会报错的,它默认需要接收一个对象
如果我们需要实现传入函数,那我们就需要告诉:你只需要默默的帮我执行以下这个函数就好!
这时我们就需要引入中间件,在原生的 redux
中暴露出 applyMiddleware
中间件执行函数,并引入 redux-thunk
中间件(需要手动下载)
import thunk from 'redux-thunk'
通过第二个参数传递下去就可以了
export default createStore(countReducer, applyMiddleware(thunk))
注意:异步 action 不是必须要写的,完全可以自己等待异步任务的结果后再去分发同步action
采用
react-thunk
能让异步代码像同步代码一样执行,在redux
中我们也是可以实现异步的,但是这样我们的代码中会有很多异步的细节,这不是我们想看到的,利用react-thunk
之类的库,就能让我们只关心我们的业务
6. Redux 三大原则
理解好 Redux 有助于我们更好的理解接下来的 React -Redux
第一个原则
单向数据流:整个 Redux 中,数据流向是单向的
UI 组件 —> action —> store —> reducer —> store
第二个原则
state 只读:在 Redux 中不能通过直接改变 state ,来控制状态的改变,如果想要改变 state ,则需要触发一次 action。通过 action 执行 reducer
第三个原则
纯函数执行:每一个reducer 都是一个纯函数,不会有任何副作用,返回是一个新的 state,state 改变会触发 store 中的 subscribe
参考资料
Redux + React-router 的入门📖和配置👩🏾💻教程
小册:React 进阶实践指南
非常感谢您的阅读,欢迎提出你的意见,有什么问题欢迎指出,谢谢!🎈
React 入门学习(十五)– React-Redux 基本使用
引言
在前面我们学习了 Redux ,我们在写案例的时候,也发现了它存在着一些问题,例如组件无法状态无法公用,每一个状态组件都需要通过订阅来监视,状态更新会影响到全部组件更新,面对着这些问题,React 官方在 redux 基础上提出了 React-Redux 库
在前面的案例中,我们如果把 store 直接写在了 React 应用的顶层 props 中,各个子组件,就能访问到顶层 props
<顶层组件 store={store}>
<App />
</顶层组件/>
这就类似于 React-Redux
容器组件和 UI 组件
- 所有的 UI 组件都需要有一个容器组件包裹
- 容器组件来负责和 Redux 打交道,可以随意使用 Redux 的API
- UI 组件无任何 Redux API
- 容器组件用于处理逻辑,UI 组件只会负责渲染和交互,不处理逻辑
在我们的生产当中,我们可以直接将 UI 组件写在容器组件的代码文件当中,这样就无需多个文件
首先,我们在 src 目录下,创建一个 containers
文件夹,用于存放各种容器组件,在该文件夹内创建 Count
文件夹,即表示即将创建 Count 容器组件,再创建 index.jsx
编写代码
要实现容器组件和 UI 组件的连接,我们需要通过 connect
来实现
// 引入UI组件
import CountUI from '../../components/Count'
// 引入 connect 连接UI组件
import {connect} from 'react-redux'
// 建立连接
export default connect()(CountUI)
后面还会详细讲到
Provider
由于我们的状态可能会被很多组件使用,所以 React-Redux 给我们提供了一个 Provider 组件,可以全局注入 redux 中的 store ,只需要把 Provider 注册在根部组件即可
例如,当以下组件都需要使用 store 时,我们需要这么做,但是这样徒增了工作量,很不便利
<Count store={store}/>
{/* 示例 */}
<Demo1 store={store}/>
<Demo1 store={store}/>
<Demo1 store={store}/>
<Demo1 store={store}/>
<Demo1 store={store}/>
我们可以这么做:在 src 目录下的 index.js
文件中,引入 Provider
,直接用 Provider
标签包裹 App
组件,将 store
写在 Provider
中即可
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
这样我们在 App.jsx
文件中,组件无需手写指定 store
,即可使用 store
connect
在前面我们看到的 react-redux 原理图时,我们会发现容器组件需要给 UI 组件传递状态和方法,并且是通过 props
来传递,看起来很简单。但是,我们会发现容器组件中似乎没有我们平常传递 props
的情形
这时候就需要继续研究一下容器组件中的唯一一个函数 connect
connect 方法是一个连接器,用于连接容器组件和 UI 组件,它第一次执行时,接收4个参数,这些参数都是可选的,它执行的执行的结果还是一个函数,第二次执行接收一个 UI 组件
第一次执行时的四个参数:mapStateToProps
、mapDispatchToProps
、mergeProps
、options
mapStateToProps
const mapStateToProps = state => ({ count: state })
它接收 state
作为参数,并且返回一个对象,这个对象标识着 UI 组件的同名参数,
返回的对象中的 key 就作为传递给 UI 组件 props 的 key,value 就作为 props 的 value
如上面的代码,我们可以在 UI 组件中直接通过 props 来读取 count
值
<h1>当前求和为:{this.props.count}</h1>
这样我们就打通了 UI 组件和容器组件间的状态传递,那如何传递方法呢?
mapDispatchToProps
connect 接受的第二个参数是 mapDispatchToProps
它是用于建立 UI 组件的参数到 store.dispacth
方法的映射
我们可以把参数写成对象形式,在这里面定义 action 执行的方法,例如 jia
执行什么函数,jian
执行什么函数?
我们都可以在这个参数中定义,如下定义了几个方法对应的操作函数
{
jia: createIncrementAction,
jian: createDecrementAction,
jiaAsync: createIncrementAsyncAction
}
写到这里其实 connect
已经比较完善了,但是你可以仔细想想 redux
的工作流程
似乎少了点什么,我们在这里调用了函数,创建了 action
对象,但是好像 store
并没有执行 dispatch
,那是不是断了呢?执行不了呢?
其实这里 react-redux
已经帮我们做了优化,当调用 actionCreator
的时候,会立即发送 action
给 store
而不用手动的 dispatch
- 自动调用 dispatch
完整开发
首先我们在 containers
文件夹中,直接编写我们的容器组件,无需编写 UI 组件
先打 rcc
打出指定代码段,然后暴露出 connect
方法
import { connect } from 'react-redux'
从 action
文件中暴露创建 action
的方法
import {createIncrementAction} from '../../redux/count_action'
编写 UI 组件,简单写个 demo,绑定 props 和方法
return (
<div>
<h2>当前求和为:{this.props.count}</h2>
<button onClick={this.add}>点我加1</button>
</div>
);
调用 connect
包装暴露 UI 组件
export default connect(
state => ({ count: state }),// 状态
{ jia: createIncrementAction } // 方法
)(Count);
第一次执行的参数就直接传递 state
和一个指定 action
的对象
非常感谢您的阅读,欢迎提出你的意见,有什么问题欢迎指出,谢谢!🎈
React 入门学习(十六)– 数据共享
引言
在写完了基本的 Redux 案例之后,我们可以尝试一些更实战性的操作,比如我们可以试试多组件间的状态传递,相互之间的交互
如上动图所示,我们想要实现上面的案例,采用纯 React 来实现是比较困难的,我们需要很多层的数据交换才能实现,但是我们如果采用 Redux 来实现会变得非常简单
因为 Redux 打通了组件间的隔阂,我们可以自由的进行数据交换,所有存放在 store
中的数据都可以实现共享,那我们接下来看看如何实现的吧~
1. 编写 Person 组件
上面的 Count 组件,已经在前面几篇写过了,但是我没有记录详细的实现过程,只是做了一些小小的总结(我摸鱼了)
不管如何,我们先来实现一个 Person 组件吧
首先我们需要在 containers
文件夹下编写 Person 组件的容器组件
如何编写一个容器组件呢?(上一篇也讲过了)
首先我们需要编写 index.jsx
文件,在这个文件里面编写 Person 组件的 UI 组件,并使用 connect
函数将它包装,映射它的状态和方法
编写 UI 组件架构
<div>
<h2>我是 Person 组件,上方组件求和为:{this.props.countAll}</h2>
<input ref={c => this.nameNode = c} type="text" placeholder="输入名字" />
<input ref={c => this.ageNode = c} type="text" placeholder="输入年龄" />
<button onClick={this.addPerson}>添加</button>
<ul>
{
this.props.persons.map((p) => {
return <li key={p.id}> {p.name}--{p.age}</li>
})
}
</ul>
</div>
我们可以看到这里采用了 ref
来获取到当前事件触发的节点,并通过 this.addPerson
的方式给按钮绑定了一个点击事件
编写点击事件回调
addPerson = () => {
const name = this.nameNode.value
const age = this.ageNode.value
const personObj = { id: nanoid(), name, age }
this.props.add(personObj)
this.nameNode.value = ''
this.ageNode.value = ''
}
在这里我们需要处理输入框中的数据,并且将这些数据用于创建一个 action
对象,传递给 store
进行状态的更新
在这里我们需要回顾的是,这里我们使用了一个 nanoid
库,这个库我们之前也有使用过
下载,引入,暴露
import { nanoid } from 'nanoid'
暴露的 nanoid
是一个函数,我们每一次调用时,都会返回一个不重复的数,用于确保 id
的唯一性,同时在后面的 map
遍历的过程中,我们将 id
作为了 key
值,这样也确保了 key
的唯一性,关于 key
的作用,可以看看 diffing
算法的文章
状态管理
在这里我们需要非常熟练的采用 this.props.add
的方式来更新状态
那么它是如何实现状态更新的呢?我们来看看
在我们调用 connect
函数时,我们第一次调用时传入的第二个参数,就是用于传递方法的,我们传递了一个 add
方法
export default connect(
state => ({ persons: state.person, countAll: state.count }),//映射状态
{ add: createAddPersonAction }
)(Person);
它的原词是:mapDispatchToProps
我的理解是,传入的东西会被映射映射成 props
对象下的方法,这也是我们能够在 props
下访问到 add
方法的原因
对于这一块
connect
,我们必须要能够形成自己的理解,这里非常的重要,它实现了数据的交互,不至于一个组件,而是全部组件
我是如何理解的呢?
想象一个 store 仓库,在我们这个案例当中,Count 组件需要存放 count 值在 store 中,Person 组件需要存放新增用户对象在 store 中,我们要把这两个数据存放在一个对象当中。当某个组件需要使用 store 中的值时,可以通过 connect 中的两个参数来获取,例如这里我们需要使用到 Count 组件的值,可以通过
.count
来从 store 中取值。
也就是说,所有的值都存放在 store 当中,通过点运算符来获取,所有的操作 store 的方法都需要通过 action 来实现。当前组件需要使用的数据都需要在 connect
中暴露
2. 编写 reducer
首先,我们需要明确 reducer 的作用,它是用来干什么的?
根据操作类型来指定状态的更新
也就是说当我们点击了添加按钮后,会将输入框中的数据整合成一个对象,作为当前 action 对象的 data 传递给 reducer
我们可以看看我们编写的 action 文件,和我们想的一样
import { ADD_PERSON } from "../constant";
// 创建一个人的action 对象
export const createAddPersonAction = (personObj) => ({
type: ADD_PERSON,
data: personObj,
});
当 reducer 接收到 action 对象时,会对 type 进行判断
export default function personReducer(preState = initState, action) {
const { type, data } = action;
switch (type) {
case ADD_PERSON:
return [data,...preState]
default:
return preState
}
}
一般都采用 switch
来编写
这里有个值得注意的地方是,这个 personReducer
函数是一个纯函数,什么是纯函数呢?这个是高阶函数部分的知识了,纯函数是一个不改变参数的函数,也就是说,传入的参数是不能被改变的。
为什么要提这个呢?在我们 return 时,有时候会想通过数组的 API 来在数组前面塞一个值,不也可以吗?
但是我们要采用 unshirt
方法,这个方法是会改变原数组的,也就是我们传入的参数会被改变,因此这样的方法是不可行的!
3. 打通数据共享
写到这里,或许已经写完了,但是有些细节还是需要注意一下
采用 Redux 来进行组件的数据交互真的挺方便。
我们可以在 Count 组件中引入 Person 组件存在 store 中的状态。
export default connect(state => ({ count: state.count, personNum: state.person.length }),
{
...
}
)(Count)
在这里我们将 store 中的 person 数组的长度暴露出来这样 Count 组件就可以直接通过 props 来使用了
同样的我们也可以在 Person 组件中使用 Count 组件的值
从而实现了我们的这个 Demo
4. 最终优化
- 利用对象的简写方法,将键名和键值同名,从而只写一个名即可
- 合并 reducer ,我们可以将多个 reducer文件 写在一个 index 文件当中,需要采用
combineReducers
来合并
5. 项目打包
执行 npm run build
命令,即可打包项目,打包完成后,会生成一个 build
文件,这个文件我们需要部署到服务器上才能运行
我们可以放在自己的服务器上即可
但是我遇到了一个问题
打包后的文件路径少了一个 .
导致文件无法找到,报错无法执行,我通过手动添加的方式解决了,不知道还有没有什么其他方法解决
也可以采用
npm i serve -g
安装,如何通过 serve ‘指定文件夹’ 来执行
非常感谢您的阅读,欢迎提出你的意见,有什么问题欢迎指出,谢谢!🎈
React 入门学习(十七)– React 扩展
引言
学到这里 React 已经学的差不多了,接下来就学习一些 React 扩展内容,可以帮助我们更好的开发和理解,这部分的知识还有很多的东西可以探寻,比如:网红 React-Hook,就是我们需要注意的地方,打了 100 多集的类式组件,出来一个 hooks ,现在用函数式组件偏多了………….
所以 Hooks 就需要我们深入的学习一下了,下面我们就一起来看看扩展部分有哪些内容吧
1. setState
对象式 setState
首先在我们以前的认知中,setState
是用来更新状态的,我们一般给它传递一个对象,就像这样
this.setState({
count: count + 1
})
这样每次更新都会让 count
的值加 1。这也是我们最常做的东西
这里我们做一个案例,点我加 1,一个按钮一个值,我要在控制台输出每次的 count
的值
那我们需要在控制台输出,要如何实现呢?
我们会考虑在 setState
更新之后 log
一下
add = () => {
const { count } = this.state
this.setState({
count: count + 1
})
console.log(this.state.count);
}
因此可能会写出这样的代码,看起来很合理,在调用完 setState
之后,输出 count
我们发现显示的 count
和我们控制台输出的 count
值是不一样的
这是因为,我们调用的 setState
是同步事件,但是它的作用是让 React 去更新数据,而 React 不会立即的去更新数据,这是一个异步的任务,因此我们输出的 count
值会是状态更新之前的数据。“React 状态更新是异步的”
那我们要如何实现同步呢?
其实在 setState
调用的第二个参数,我们可以接收一个函数,这个函数会在状态更新完毕并且界面更新之后调用,我们可以试试
add = () => {
const { count } = this.state
this.setState({
count: count + 1
}, () => {
console.log(this.state.count)
})
}
我们将 setState
填上第二个参数,输出更新后的 count
值
这样我们就能成功的获取到最新的数据了,如果有这个需求我们可以在第二个参数输出噢~
函数式 setState
这种用法我也是第一次见,函数式的 setState
也是接收两个参数
第一个参数是 updater
,它是一个能够返回 stateChange
对象的函数
第二个参数是一个回调函数,用于在状态更新完毕,界面也更新之后调用
与对象式 setState
不同的是,我们传递的第一个参数 updater
可以接收到2个参数 state
和 props
我们尝试一下
add = () => {
this.setState((state) => ({ count: state.count + 1 }))
}
我们也成功的实现了
我们在第一个参数中传入了一个函数,这个函数可以接收到 state
,我们通过更新 state
中的 count
值,来驱动页面的更新
利用函数式 setState
的优势还是很不错的,可以直接获得 state
和 props
可以理解为对象式的
setState
是函数式setState
的语法糖
2. LazyLoad
懒加载在 React 中用的最多的就是路由组件了,页面刷新时,所有的页面都会重新加载,这并不是我们想要的,我们想要实现点击哪个路由链接再加载即可,这样避免了不必要的加载
我们可以发现,我们页面一加载时,所有的路由组件都会被加载
如果我们有 100 个路由组件,但是用户只点击了几个,这就会有很大的消耗,因此我们需要做懒加载处理,我们点击哪个时,才去加载哪一个
首先我们需要从 react
库中暴露一个 lazy
函数
import React, { Component ,lazy} from 'react';
然后我们需要更改引入组件的方式
const Home = lazy(() => import('./Home'))
const About = lazy(() => import('./About'))
采用 lazy
函数包裹
我们会遇到这样的错误,提示我们用一个标签包裹
这里是因为,当我们网速慢的时候,路由组件就会有可能加载不出来,页面就会白屏,它需要我们来指定一个路由组件加载的东西,相对于 loading
<Suspense fallback={<h1>loading</h1>}>
<Route path="/home" component={Home}></Route>
<Route path="/about" component={About}></Route>
</Suspense>
在做这个案例的时候,一定不要设置重定向的东西,所有的路由我们要点击再加载
初次登录页面的时候
注意噢,这些文件都不是路由组件,当我们点击了对应组件之后才会加载
从上图我们可以看出,每次点击时,才会去请求 chunk
文件
那我们更改写的 fallback
有什么用呢?它会在页面还没有加载出来的时候显示
注意:因为 loading 是作为一个兜底的存在,因此 loading 是 必须提前引入的,不能懒加载
3. Hooks
useState
hooks
解决了函数式组件和类式组件的差异,让函数式组件拥有了类式组件所拥有的 state
,同时新增了一些 API ,让函数式组件,变得更加的灵活
首先我们需要明确一点,函数式组件没有自己的 this
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 的函数
我们先看看控制台输出的什么
count
是初始化的值,而 setCount
就像是一个 action
对象驱动状态更新
我们可以通过 setCount
来更新 count
的值
setCount(count + 1)
useEffect
在类式组件中,提供了一些声明周期钩子给我们使用,我们可以在组件的特殊时期执行特定的事情,例如 componentDidMount
,能够在组件挂载完成后执行一些东西
在函数式组件中也可以实现,它采用的是 effectHook
,它的语法更加的简单,同时融合了 componentDidUpdata
生命周期,极大的方便了我们的开发
React.useEffect(() => {
console.log('被调用了');
})
由于函数的特性,我们可以在函数中随意的编写函数,这里我们调用了 useEffect
函数,这个函数有多个功能
当我们像上面代码那样使用时,它相当于 componentDidUpdata
和 componentDidMount
一同使用,也就是在组件挂载和组件更新的时候都会调用这个函数
它还可以接收第二个参数,这个参数表示它要监测的数据,也就是他要监视哪个数据的变化
当我们不需要监听任何状态变化的时候,我们可以就传递一个空数组,这样它就能当作 componentMidMount
来使用
React.useEffect(() => {
console.log('被调用了');
}, [])
这样我们只有在组件第一次挂载的时候触发
当然当页面中有多个数据源时,我们也可以选择个别的数据进行监测以达到我们想要的效果
React.useEffect(() => {
console.log('被调用了');
}, [count])
这样,我们就只监视 count 数据的变化
当我们想要在卸载一个组件之前进行一些清除定时器的操作,在类式组件中,我们会调用生命周期钩子 componentDidUnmount
来实现,在函数式组件中,我们的写法更为简单,我们直接在 useEffect
的第一个参数的返回值中实现即可
也就是说,第一个参数的函数体相当于 componentDidMount
返回体相当于 componentDidUnmount
,这样我们就能实现在组件即将被卸载时输出一些东西了
实现卸载
function unmount() {
ReactDOM.unmountComponentAtNode(document.getElementById("root"))
}
卸载前输出
React.useEffect(() => {
console.log('被调用了');
return () => {
console.log('我要被卸载了');
}
}, [count])
实现了在组件即将被卸载的时候输出
因此 useEffect
相当于三个生命周期钩子,componentDidMount
、componentDidUpdata
、componentDidUnmount
useRef
当我们想要获取组件内的信息时,在类式组件中,我们会采用 ref
的方式来获取。在函数式组件中,我们可以采用也可以采用 ref
但是,我们需要采用 useRef
函数来创建一个 ref 容器,这和 createRef
很类似。
<input type="text" ref={myRef} />
获取 ref 值
function show() {
alert(myRef.current.value)
}
即可成功的获取到 input 框中的值
4. Fragment
我们编写组件的时候每次都需要采用一个 div
标签包裹,才能让它正常的编译,但是这样会引发什么问题呢?我们打开控制台看看它的层级
它包裹了几层无意义的 div 标签,我们可以采用 Fragment
来解决这个问题
首先,我们需要从 react 中暴露出 Fragment
,将我们所写的内容采用 Fragment
标签进行包裹,当它解析到 Fragment
标签的时候,就会把它去掉
这样我们的内容就直接挂在了 root
标签下
同时采用空标签,也能实现,但是它不能接收任何值,而
Fragment
能够接收 1 个值key
5. Context
仅适用于类式组件
当我们想要给子类的子类传递数据时,前面我们讲过了 redux 的做法,这里介绍的 Context 我觉得也类似于 Redux
首先我们需要引入一个 MyContext
组件,我们需要引用MyContext
下的 Provider
const MyContext = React.createContext();
const { Provider } = MyContext;
用 Provider
标签包裹 A组件内的 B 组件,并通过 value
值,将数据传递给子组件,这样以 A 组件为父代组件的所有子组件都能够接受到数据
<Provider value={{ username, age }}>
<B />
</Provider>
但是我们需要在使用数据的组件中引入 MyContext
static contextType = MyContext;
在使用时,直接从 this.context
上取值即可
const {username,age} = this.context
适用于函数和类式组件
由于函数式组件没有自己 this
,所以我们不能通过 this.context
来获取数据
这里我们需要从 Context
身上暴露出一个 Consumer
const { Provider ,Consumer} = MyContext;
然后通过 value
取值即可
function C() {
return (
<div>
<h3>我是C组件,我从A接收到的数据 </h3>
<Consumer>
{(value) => {
return `${value.username},年龄是${value.age}`;
}}
</Consumer>
</div>
);
}
因此想要在函数式组件中使用,需要引入
Consumer
6. PureComponent
在我们之前一直写的代码中,我们一直使用的Component
是有问题存在的
- 只要执行
setState
,即使不改变状态数据,组件也会调用render
- 当前组件状态更新,也会引起子组件
render
而我们想要的是只有组件的 state
或者 props
数据发生改变的时候,再调用 render
我们可以采用重写 shouldComponentUpdate
的方法,但是这个方法不能根治这个问题,当状态很多时,我们没有办法增加判断
我们可以采用 PureComponent
我们可以从 react
身上暴露出 PureComponent
而不使用 Component
import React, { PureComponent } from 'react'
就这~听了半天结果就只一个 PureComponent
PureComponent
会对比当前对象和下一个状态的 prop
和 state
,而这个比较属于浅比较,比较基本数据类型是否相同,而对于引用数据类型,比较的是它的引用地址是否相同,这个比较与内容无关
7. render props
采用 render props 技术,我们可以像组件内部动态传入带有内容的结构
当我们在一个组件标签中填写内容时,这个内容会被定义为 children props,我们可以通过
this.props.children
来获取
例如:
<A>hello</A>
这个 hello 我们就可以通过 children 来获取
而我们所说的 render props 就是在组件标签中传入一个 render 方法,又因为属于 props ,因而被叫做了 render props
<A render={(name) => <C name={name} />} />
你可以把 render
看作是 props
,只是它有特殊作用,当然它也可以用其他名字来命名
在上面的代码中,我们需要在 A 组件中预留出 C 组件渲染的位置 在需要的位置上加上{this.props.render(name)}
那我们在 C 组件中,如何接收 A 组件传递的 name
值呢?通过 this.props.name
的方式
8. ErrorBoundary
当不可控因素导致数据不正常时,我们不能直接将报错页面呈现在用户的面前,由于我们没有办法给每一个组件、每一个文件添加判断,来确保正常运行,这样很不现实,因此我们要用到错误边界技术
错误边界就是让这块组件报错的影响降到最小,不要影响到其他组件或者全局的正常运行
例如 A 组件报错了,我们可以在 A 组件内添加一小段的提示,并把错误控制在 A 组件内,不影响其他组件
- 我们要对容易出错的组件的父组件做手脚,而不是组件本身
我们在父组件中通过 getDerivedStateFromError
来配置子组件出错时的处理函数
static getDerivedStateFromError(error) {
console.log(error);
return { hasError: error }
}
我们可以将 hasError
配置到状态当中,当 hasError
状态改变成 error
时,表明有错误发生,我们需要在组件中通过判断 hasError
值,来指定是否显示子组件
{this.state.hasError ? <h2>出错啦</h2> : <Child />}
在服务器中启动,才能正常看到效果
可以在 componentDidCatch
中统计错误次数,通知编码人员进行 bug 解决
9. 组件通信方式总结
- props
- children props
- render props
- 消息发布订阅
- 利用 pubsub 库来实现
- 集中式状态管理
- redux
- conText
- 生成者-消费者
选择方式
父子组件采用:props
兄弟组件采用:消息的发布订阅、redux
祖孙组件:消息发布订阅、redux、context
React核心 – React-Hooks
hooks 存在的意义
hooks 之间的状态是独立的,有自己独立的上下文,不会出现混淆状态的情况
让函数有了状态管理
解决了 组件树不直观、类组件难维护、逻辑不易复用的问题
避免函数重复执行的副作用
应用场景
- 利用 hooks 取代生命周期函数
- 让组件有了状态
- 组件辅助函数
- 处理发送请求
- 存取数据
- 做好性能优化
hooks API
从 react
中引入
1. useState
给函数组件添加状态
- 初始化以及更新组件状态
const [count, setCount] = React.useState(0)
接收一个参数作为初始值,返回一个数组:第一个是状态变量,第二个是修改变量的函数
2. useEffect
副作用 hooks
- 给没有生命周期的组件,添加结束渲染的信号
注意:
- render 之后执行的 hooks
第一个参数接收一个函数,在组件更新的时候执行
第二个参数接收一个数组,用来表示需要追踪的变量,依赖列表,只有依赖更新的时候才会更新内容
第一个参数的返回值,返回一个函数,在 useEffect
执行之前,都会先执行里面返回的函数
一般用于添加销毁事件,这样就能保证只添加一个
React.useEffect(() => {
console.log('被调用了');
return () => {
console.log('我要被卸载了');
}
}, [count])
打印
3. useLayoutEffect
和 useEffect
很类似
它的作用是:在 DOM 更新完成之后执行某个操作
注意:
- 有 DOM 操作的副作用 hooks
- 在 DOM 更新之后执行
执行时机在
useEffect
之前,其他都和useEffect
都相同
useEffect
执行时机在 render 之后
useLayoutEffect
执行时机在 DOM 更新之后
4. useMemo
作用:让组件中的函数跟随状态更新
注意:优化函数组件中的功能函数
为了避免由于其他状态更新导致的当前函数的被迫执行
第一个参数接收一个函数,第二个参数为数组的依赖列表,返回一个值
const getDoubleNum = useMemo(() => {
console.log('ddd')
return 2 * num
}, [num])
5. useCallback
作用:跟随状态更新执行
注意:
- 只有依赖项改变时才执行
useMemo( () => fn, deps)
相当于useCallback(fn, deps)
不同点:
useCallback
返回的是一个函数,不再是值useCallback
缓存的是一个函数,useMemo
缓存的是一个值,如果依赖不更新,返回的永远是缓存的那个函数- 给子组件中传递
props
的时候,如果当前组件不更新,不会触发子组件的重新渲染
6. useRef
作用:长久保存数据
注意事项:
- 返回一个子元素索引,这个索引在整个生命周期中保持不变
- 对象发生改变时,不通知,属性变更不重新渲染
- 保存一个值,在整个生命周期中维持不变
- 重新赋值
ref.current
不会触发重新渲染 - 相当于创建一个额外的容器来存储数据,我们可以在外部拿到这个值
当我们通过正常的方式去获取计时器的 id
是无法获取的,需要通过 ref
useEffect(() => {
ref.current = setInterval(() => {
setNum(num => num + 1)
}, 400)
}, [])
useEffect(() => {
if (num > 10) {
console.log('到十了');
clearInterval(ref.current)
}
}, [num])
7. useContext
作用:带着子组件渲染
注意:
- 上层数据发生改变,肯定会触发重新渲染
- 我们需要引入
useContext
和createContext
两个内容 - 通过
createContext
创建一个Context
句柄 - 通过
Provider
确定数据共享范围 - 通过
value
来分发数据 - 在子组件中,通过
useContext
来获取数据
import React, { useContext, createContext } from 'react'
const Context = createContext(null)
export default function Hook() {
const [num, setNum] = React.useState(1)
return (
<h1>
这是一个函数组件 - {num}
// 确定范围
<Context.Provider value={num}>
<Item1 num={num} />
<Item2 num={num} />
</Context.Provider>
</h1>
)
}
function Item1() {
const num = useContext(Context)
return <div>子组件1 {num}</div>
}
function Item2() {
const num = useContext(Context)
return <div>子组件2 {num}</div>
}
8. useReducer
作用:去其他地方借资源
注意:函数组件的 Redux 的操作
- 创建数据仓库
store
和管理者reducer
- 通过
useReducer(store,dispatch)
来获取state
和dispatch
const store = {
num: 10
}
const reducer = (state, action) => {
switch (action.type) {
case "":
return
default:
return
}
}
const [state, dispatch] = useReducer(reducer, store)
通过 dispatch
去派发 action
9. 自定义 hooks
放在 utils
文件夹中,以 use
开头命名
例如:模拟数据请求的 Hooks
import React, { useState, useEffect } from "react";
function useLoadData() {
const [num, setNum] = useState(1);
useEffect(() => {
setTimeout(() => {
setNum(2);
}, 1000);
}, []);
return [num, setNum];
}
export default useLoadData;
减少代码耦合
我们希望 reducer 能让每个组件来使用,我们自己写一个 hooks
自定义一个自己的 LocalReducer
import React, { useReducer } from "react";
const store = { num: 1210 };
const reducer = (state, action) => {
switch (action.type) {
case "num":
return { ...state, num: action.num };
default:
return { ...state };
}
};
function useLocalReducer() {
const [state, dispatch] = useReducer(reducer, store);
return [state, dispatch];
}
export default useLocalReducer;
- 引入 react 和自己需要的 hook
- 创建自己的hook函数
- 返回一个数组,数组中第一个内容是数据,第二个是修改数据的函数
- 暴露自定义 hook 函数出去
- 引入自己的业务组件