注:Redux最新用法参考 个人React专栏react 初级学习
Hooks基本介绍-------------------------
-
Hooks
:钩子、钓钩、钩住, Hook 就是一个特殊的函数,让你在函数组件中获取状态等 React 特性 ,是 React v16.8 中的新增功能 -
作用:为函数组件提供状态、生命周期等原本 class 组件中提供的 React 功能
- 可以理解为通过 Hooks 为函数组件钩入 class 组件的特性
-
注意:Hooks 只能在函数组件中使用,自此,函数组件成为 React 的新宠儿
React v16.8 版本前后,组件开发模式的对比:
-
React v16.8 以前: class 组件(提供状态) + 函数组件(展示内容)
-
React v16.8 及其以后:
-
class 组件(提供状态) + 函数组件(展示内容)
-
Hooks(提供状态) + 函数组件(展示内容)
-
混用以上两种方式:部分功能用 class 组件,部分功能用 Hooks+函数组件
-
总结:
注意1:虽然有了 Hooks,但 React 官方并没有计划从 React 库中移除 class
注意2:有了 Hooks 以后,不能再把函数组件称为无状态组件了,因为 Hooks 为函数组件提供了状态
为什么要有 Hooks
-
组件的状态逻辑复用
-
在 Hooks 之前,组件的状态逻辑复用经历了:mixins(混入)、HOCs(高阶组件)、render-props 等模式
-
(早已废弃)mixins 的问题:1 数据来源不清晰 2 命名冲突
-
HOCs、render-props 的问题:重构组件结构,导致组件形成 JSX 嵌套地狱问题
-
-
class 组件自身的问题
-
选择:函数组件和 class 组件之间的区别以及使用哪种组件更合适
-
需要理解 class 中的 this 是如何工作的
-
相互关联且需要对照修改的代码被拆分到不同生命周期函数中
-
相比于函数组件来说,不利于代码压缩和优化,也不利于 TS 的类型推导
-
注意:
之前的react语法并不是以后就不用了,class 组件相关的 API 在hooks中可以不用
- class 自身语法,比如,constructor、static 等
- 钩子函数,
componentDidMount
、componentDidUpdate
、componentWillUnmount
this
相关的用法
useState-Hooks
useState-基本使用
-
useState
作用:为函数组件提供状态(state),不能在类组件中调用 -
useState
使用场景:当你想要在函数组件中,使用组件状态时 ,就要使用 useState Hook 了 -
约定:修改状态的函数名称以 set 开头,后面跟上状态的名称
-
多次调用 useState 多个状态和修改状态的函数之间不会相互影响
-
useState
提供的状态,是函数内部的局部变量,可以在函数内的任意位置使用 -
每次渲染,useState 获取到的都是最新的状态值(react会记住最新的状态值),useState 的初始值(参数)只会在组件第一次渲染时生效。
语法:
javascript
import { useState } from 'react'
// 参数:状态初始值可以是任意值
// 返回值:stateArray 是一个数组
const stateArray = useState(0)
// 索引 0 表示:状态值(state)
const state = stateArray[0]
// 索引 1 表示:修改状态的函数(setState(newValue)` 是一个函数,参数表示:*新的状态值*)
const setState = stateArray[1]
状态的读取和修改:
- 读取状态
const Counter = () => {
const [user, setUser] = useState({ name: 'jack', age: 18 })
return (
<div>
<p>姓名:{user.name}</p>
<p>年龄:{user.age}</p>
</div>
)
}
- 修改状态
- 调用该函数后,将使用新的状态值
替换
旧值 - 修改状态后,因为状态发生了改变,所以,该组件会重新渲染
setUser(newValue)
是一个函数,参数表示:新的状态值 。调用这个函数,新的状态值会覆盖原来的状态值,所以,这里和class的 setState不一样,区分一下,class的setState需要修改哪个值就传哪个值,内部会做状态的合并,hooks呢修改状态值会直接覆盖不会合并,所以hooks修改状态值需要先对原来的值进行取值解构,再修改。
- 调用该函数后,将使用新的状态值
javascript
const Counter = () => {
// 利用数组解构提取状态和修改状态
const [user, setUser] = useState({ name: 'jack', age: 18 })
const onAgeAdd = () => {
setUser({
...user,
age: user.age + 1
})
}
return (
<div>
<p>姓名:{user.name}</p>
<p>年龄:{user.age}</p>
<button onClick={onAgeAdd}>年龄+1</button>
</div>
)
}
useState-使用规则
-
如何为函数组件提供多个状态?
-
调用
useState
Hook 多次即可,每调用一次 useState Hook 可以提供一个状态 -
useState Hook
多次调用返回的 [state, setState],相互之间,互不影响
-
-
useState 等 Hook 的使用规则:
-
React Hooks 只能直接出现在 函数组件 中
-
React Hooks不能嵌套在 if/for/其他函数 中
-
原理:React 是按照 Hooks 的调用顺序来识别每一个 Hook,如果每次调用的顺序不同,导致 React 无法知道是哪一个 Hook
-
useEffect-Hooks
useEffect-基本含义
- 作用:处理函数组件中的副作用(side effect)
- 副作用是相对于主作用来说的,一个功能(比如,函数)除了主作用,其他的作用就是副作用 对于 React 组件来说,主作用就是根据数据(state/props)渲染 UI,除此之外都是副作用(比如,手动修改 DOM)
- 常见的副作用(side effect):数据(Ajax)请求、手动修改 DOM、localStorage、console.log 操作等
- useEffect完全指南:useEffect 完整指南 --- Overreacted
useEffect-基本使用
使用时机
javascript
import { useEffect } from 'react'
// 1
// 触发时机: 第一次渲染会执行 、 每次组件重新渲染都会再次执行
// componentDidMount + ComponentDidUpdate
useEffect(() => {})
// 2(使用频率最高)
// 触发时机:只在组件第一次渲染时执行
// componentDidMount
useEffect(() => {}, [])
// 3(使用频率最高)
// 触发时机:1 第一次渲染会执行 2 当 count(某个状态) 变化时才会次执行
// componentDidMount + componentDidUpdate(判断 count 有没有改变)
useEffect(() => {}, [count])
// 4
useEffect(() => {
// 返回值函数的执行时机:组件卸载时
// 在返回的函数中,清理工作
return () => {
// 相当于 componentWillUnmount
}
}, [])
//5
useEffect(() => {
// 返回值函数的执行时机:1 组件卸载时 2 count 变化时
// 在返回的函数中,清理工作
return () => {}
}, [count])
发送请求
-
在组件中,可以使用 useEffect Hook 来发送请求(side effect)获取数据
-
注意:effect 只能是一个同步函数,不能使用 async
- 因为如果 effect 是 async 的,此时返回值是 Promise 对象。这样的话,就无法保证清理函数被立即调用
-
为了使用 async/await 语法,可以在 effect 内部创建 async 函数,并调用
javascript
// 错误演示:不要给 effect 添加 async
useEffect(async () => {
const res = await axios.get('http://xxx')
return () => {}
}, [])
// 正确使用
useEffect(() => {
const loadData = async () => {
const res = await axios.get('http://xxx')
}
loadData()
return () => {}
}, [])
useContext-Hooks
作用
- 在函数组件中,获取 Context 中的值。要配合 Context 一起使用。
语法
-
useContext 的参数:Context 对象,即:通过 createContext 函数创建的对象
-
useContext 的返回值:Context.Provider 中提供的 value 数据
import { useContext } from 'react'
const { color } = useContext(ColorContext)
useContext Hook
与<Context.Consumer>
的区别:获取数据的位置不同
-
<Context.Consumer>
:在 JSX 中获取 Context 共享的数据 -
useContext:在 JS 代码 或者 JSX 中获取 Context 的数据
const ColorContext = createContext()
const Child = () => {
// 在普通的 JS 代码中:
const { color } = useContext(ColorContext)return ( <div> useContext 获取到的 color 值:{ color } {/* 在 JSX 中: */} <ColorContext.Consumer> {color => <span>共享的数据为:{color}</span>} </ColorContext.Consumer> </div> )
}
useState-useRef
useRef-用法
-
参数:在获取 DOM 时,一般都设置为 null(获取 DOM 对象时,如果拿不到 DOM 对象,此时,获取到的值就是 null)
-
返回值:包含 current 属性的对象。
-
注意:只要在 React 中进行 DOM 操作,都可以通过 useRef Hook 来获取 DOM(比如,获取 DOM 的宽高等)
-
注意:useRef不仅仅可以用于操作DOM,还可以操作组件
import { useRef } from 'react'
const App = () => {
// 1 使用useRef能够创建一个ref对象
const inputRef = useRef(null)const add = () => { // 3 通过 inputRef.current 来访问对应的 DOM console.log(inputRef.current.value) inputRef.current.focus() } return ( <section className="todoapp"> {/* 2 将 ref 对象设置为 input 的 ref 属性值。目的:将 ref 对象关联到 input 对应的 DOM 对象上 */} <input type="text" placeholder="请输入内容" ref={inputRef} /> <button onClick={add}>添加</button> </section> )
}
export default App
Redux基本介绍------------------------
作用: 集中式存储和管理应用的状态、处理组件通讯问题时,无视组件之间的层级关系、简化大型复杂应用中组件之间的通讯问题、数据流清晰,易于定位 Bug
Redux核心概念
为了让代码各部分职责清晰、明确,Redux 代码被分为三个核心概念:action/reducer/store
action(动作)
-
描述要做的事情,特点:只描述做什么
-
是一个js对象,必须带有type属性,用于区分动作的类型
-
根据功能的不同,可以携带额外的payload有效的载荷参数数据来完成相应功能
例如 计数器功能描述
{ type: 'increment', payload: 10 } // +10
{ type: 'decrement', payload: 10 } // -10
- 为了使action功能 多元并且灵活化,需要使用 action creator 函数去创建action,目的:简化多次使用 action 时,重复创建 action 对象,函数返回值依然是个action对象。
const decrement = payload => ({ type: 'decrement', payload })
reducer(函数)
-
用来处理 action 并更新状态,是 Redux 状态更新的地方
-
函数签名为:
(prevState, action) => newState
-
接收上一次的状态和 action 作为参数,根据 action 的类型,执行不同操作,最终返回新的状态,注意:该函数一定要有返回值,即使状态没有改变也要返回上一次的状态
-
约定:reducer 是一个纯函数(相同的输入总是得到相同的输出),并且不能包含 side effect 副作用(比如,不能修改函数参数、不能修改函数外部数据、不能进行异步操作等)
-
对于 reducer 来说,为了保证 reducer 是一个纯函数,不要:
-
不要直接修改参数 state 的值(也就是:不要直接修改当前状态,而是根据当前状态值创建新的状态值)
-
不要使用 Math.random() / new Date() / Date.now() / ajax 请求等不纯的操作
-
不要让 reducer 执行副作用(side effect)
-
// 示例:
// state 上一次的状态
// action 当前要执行的动作
const reducer = (state=10, action) => {
switch (action.type) {
// 计数器增加
case 'increment':
// 返回新状态
// return state + 1
// 根据 action 中提供的 payload 来决定到底增加多少
return state + action.payload
// 注意:一定要有 default,如果将来 reducer 无法处理某个 action,
就直接将上一次的状态返回即可
default:
return state
}
}
store(仓库)
-
整合 action 和 reducer,一个应用只有一个 store
-
维护应用的状态,获取状态:
store.getState()
-
发起状态更新时,需要分发 action:
store.dispatch(action)
-
创建 store 时接收 reducer 作为参数 :
const store = createStore(reducer)
-
订阅(监听)状态变化:
const unSubscribe = store.subscribe(() => {})
-
取消订阅状态变化:
unSubscribe()
核心代码
import {createStore} from 'redux'
// 创建 store
// 参数为:reducer 函数
const store = createStore(reducer)
// 更新状态
// dispatch 派遣,派出。表示:分发一个 action,也就是发起状态更新
store.dispatch(action)
store.dispatch( increment(2) )
// 获取状态
const state = store.getState()
// 其他 API------
// 监听状态变化
const unSubscribe = store.subscribe(() => {
// 状态改变时,执行相应操作
// 比如,记录 redux 状态
console.log(store.getState())
})
// 取消监听状态变化
unSubscribe()
Redux获取状态默认值执行过程
- 只要创建 store,给createStore(传递了 reducer),那么,Redux 就会调用一次 reducer
- Redux 内部第一次调用 reducer:
reducer的默认传值的type和payload为 (undefined, {type: "@@redux/INITv.a.4.t.t.p"})
- 因为传入的状态值是 undefined ,并且是一个随机的 action type,因为是一个随机的 action type,所以,reducer 中 switch 一定无法处理该 action,那就一定会走 default。也就是直接返回了状态的默认值:10
- Redux 内部拿到状态值store.getState()(比如,此处的 10)以后,就用这个状态值,来作为了 store 中状态的默认值
Redux代码执行流程
-
创建 store 时,Redux 就会先调用一次 reducer,来获取到默认状态(10)
-
然后分发动作
store.dispatch(action)
更新状态 -
只要调用了dispatch操作,Redux store 内部就会调用 reducer 传入:上一次的状态(当前示例中就是:
10
)和当前传入的 action({ type: 'increment' }
),计算出新的状态并返回 -
reducer 执行完毕后,将最新的状态交给 store,store 用最新的状态替换旧状态,状态更新完毕
import { createStore } from 'redux'
const store = createStore(reducer)// reducer(10, { type: 'increment' })
function reducer(state = 10, action) {
console.log('reducer:', state, action)
switch (action.type) {
case 'increment':
return state + 1
default:
return state
}
}console.log('状态值为:', store.getState()) // 10
// 发起更新状态:
// 参数: action 对象
store.dispatch({ type: 'increment' })
// 相当于: reducer(10, { type: 'increment' })console.log('更新后:', store.getState()) // 11
React-Redux介绍
react-redux 库是 Redux 官方提供的 React 绑定库,为 React 接入 Redux,实现在 React 中使用 Redux 进行状态管理。 React 和 Redux 是两个独立的库,两者之间职责独立。因此,为了实现在 React 中使用 Redux 进行状态管理 ,就需要一种机制,将这两个独立的库关联在一起。这时候就用到 React-Redux 这个绑定库了。
react-redux 的使用分为两大步:
1 全局配置(只需要配置一次)
-
安装 react-redux: yarn add react-redux
-
从 react-redux 中导入 Provider 组件
-
导入创建好的 redux 仓库
-
使用 Provider 包裹整个应用
-
将导入的 store 设置为 Provider 的 store 属性值
index.js
// 导入 Provider 组件
import { Provider } from 'react-redux'
// 导入创建好的 store
import store from './store'ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.querySelector('#root')
)
2 组件接入(获取状态或修改状态)
获取状态:useSelector hook
-
useSelector
:获取 Redux 提供的状态数据 -
参数:selector 函数,用于从 Redux 状态中筛选出需要的状态数据并返回
-
返回值:筛选出的状态
import { useSelector } from 'react-redux'
const App = () => {
const count = useSelector(state => state)return ( <div> <h1>计数器:{count}</h1> <button>数值增加</button> <button>数值减少</button> </div> )
}
修改状态:useDispatch hook
-
useDispatch
:拿到 dispatch 函数,分发 action,修改 redux 中的状态数据import { useDispatch } from 'react-redux'
const App = () => {
const dispatch = useDispatch()return ( <div> <h1>计数器:{count}</h1> {/* 调用 dispatch 分发 action */} <button onClick={() => dispatch(increment(2))}>数值增加</button> <button onClick={() => dispatch(decrement(5))}>数值减少</button> </div> )
}
总结:
-
任何一个组件都可以直接接入 Redux,也就是可以直接:1 修改 Redux 状态 2 接收 Redux 状态
-
并且,只要 Redux 中的状态改变了,所有接收该状态的组件都会收到通知,也就是可以获取到最新的 Redux 状态
-
这样的话,两个组件不管隔得多远,都可以直接通讯了
redux数据流动过程:
Redux应用
代码结构
/store --- 在 src 目录中创建,用于存放 Redux 相关的代码
/actions --- 存放所有的 action
/reducers --- 存放所有的 reducer
index.js --- redux 的入口文件,用来创建 store
Redux应用
ActionType
-
Action Type 指的是:action 对象中 type 属性的值
-
Redux 项目中会多次使用 action type,比如,action 对象、reducer 函数、dispatch(action) 等
-
目标:集中处理 action type,保持项目中 action type 的一致性
-
action type 的值采用:
'domain/action'(功能/动作)形式
,进行分类处理,比如,-
计数器:
'counter/increment'
表示 Counter 功能中的 increment 动作 -
登录:
'login/getCode'
表示登录获取验证码的动作 -
个人资料:
'profile/get'
表示获取个人资料
-
步骤:
-
在 store 目录中创建
actionTypes
目录或者constants
目录,集中处理 -
创建常量来存储 action type,并导出
-
将项目中用到 action type 的地方替换为这些常量,从而保持项目中 action type 的一致性
// actionTypes 或 constants 目录:
const increment = 'counter/increment'
const decrement = 'counter/decrement'export { increment, decrement }
// --
// 使用:
// actions/index.js
import * as types from '../acitonTypes'
const increment = payload => ({ type: types.increment, payload })
const decrement = payload => ({ type: types.decrement, payload })// reducers/index.js
import * as types from '../acitonTypes'
const reducer = (state, action) => {
switch (action.type) {
case types.increment:
return state + 1
case types.decrement:
return state - action.payload
default:
return state
}
}
- 注:额外添加 Action Type 会让项目结构变复杂,此操作可省略 ( 大型项目推荐使用 ) 。但,
domain/action
命名方式强烈推荐!
Redux应用
Reducer的分离与合并
-
随着项目功能变得越来越复杂,推荐 使用多个 reducer:按照项目功能划分,每个功能使用一个 reducer 来处理该功能的状态更新
-
项目中会有多个 reducer,但是 store 只能接收一个 reducer,因此,需要将多个 reducer 合并为一根 reducer,才能传递给 store
-
合并方式:使用 Redux 中的
combineReducers
函数 -
注意:合并后,Redux 的状态会变为一个对象,对象的结构与 combineReducers 函数的参数结构相同
- 比如,此时 Redux 状态为:
{ a: aReducer 处理的状态, b: bReducer 处理的状态 }
- 比如,此时 Redux 状态为:
-
整个 Redux 应用的状态变为了
对象
,但是,对于每个 reducer 来说,每个 reducer 只负责整个状态中的某一个值,每个reducer只负责自己要处理的状态 -
合并 reducer 后,redux 处理方式:只要合并了 reducer,不管分发什么 action,所有的 reducer 都会执行一次。各个 reducer 在执行的时候,能处理这个 action 就处理,处理不了就直接返回上一次的状态。所以,我们分发的某一个 action 就只能被某一个 reducer 来处理,也就是最终只会修改这个 reducer 要处理的状态,最终的表现就是:分发了 action,只修改了 redux 中这个 action 对应的状态!
import { combineReducers } from 'redux'
// 计数器案例,状态默认值为:0
const aReducer = (state = 0, action) => {}
// Todos 案例,状态默认值为:[]
const bReducer = (state = [], action) => {}// 合并多个 reducer 为一个 根reducer
const rootReducer = combineReducers({
aReducer: aReducer,
bReducer: bReducer
})// 创建 store 时,传入 根reducer
const store = createStore(rootReducer)// 此时,合并后的 redux 状态: { a: 0, b: [] }
reducer状态合并后,再次访问每个状态的时候,这个状态就是合并后的对象了,需要.上对象访问状态
import { useSelector } from 'react-redux'const App = () => {
const count = useSelector(state => state.aReducer)
const list = useSelector(state => state.bReducer)
}
注释:(个人针对redux的理解叙述)
- const increment= payload => ({ type: 'increment', payload })
- redux作为一个状态管理工具,将数据的状态分为了三部分,action、reducers和store
- action作为一个描述数据用途的对象, { type: 'increment', payload: 10 },这里可以将这个用途对象理解为一个标志,increment就是计数器增加的标志,表示将来需要增加的标识
- 当我们进行点击行为增加数据的时候,会调用store就会调用这个增加的标识store.dispacth(increment())
store.dispatch发起状态更新后,
只要调用了dispatch操作,Redux store 内部就会调用 reducer 并且传入上一次的状态和当前传入的 action标识,相当于会给reduce分发一个状态标志"
increment",从而使用switch函数进行标识的配对,分发正确的逻辑内容,
计算出新的状态并返回- reducer 执行完毕后,将最新的状态交给 store,store 用最新的状态替换旧状态,状态更新完
注:
redux在这里 createStore 的使用已经过时,想要了解数据分发传递的实验过程可以参考上面,新的用法逻辑提现在新的文章中...