# Redux
# 相关文档
- 英文文档: https://redux.js.org/
- 中文文档: http://www.redux.org.cn/
- Github: https://github.com/reactjs/redux
# Redux 是什么
- redux 是一个专门用于做
状态管理的 JS 库 (不是 react 插件库)。 - 它可以用在 react, angular, vue 等项目中,但基本与 react 配合使用。
- 作用:集中式管理 react 应用中多个组件
共享的状态。
# 什么时候用 Redux
- 某个组件的状态,需要让其他组件可以随时拿到(共享)。
- 一个组件需要改变另一个组件的状态(通信)。
- 总体原则:能不用就不用,如果不用比较吃力才考虑使用。
# Redux 工作流程
个人理解:Components 是客人 (点餐),Action 是服务员 (通过点餐宝把客人点的东西加工之后发送给老板),Store 是老板 (老板接收到之后给大厨),Reduce 是大厨 (大厨开始干活)

# Redux 的三个核心概念
# action
- 动作的对象
- 包含 2 个属性
type:标识属性,值为字符串,唯一,必要属性
data:数据属性,值类型任意,可选属性 - 例子:
{ type: 'ADD_STUDENT',data:{name: 'tom',age:18} }
# reducer
- 用于初始化状态、加工状态。
- 加工时,根据旧的 state 和 action, 产生新的 state 的纯函数。
# store
- 将 state、action、reducer 联系在一起的对象
- 如何得到此对象?
- import {createStore} from ‘redux’
- import reducer from ‘./reducers’
- const store = createStore(reducer)
- 此对象的功能?
- getState (): 得到 state
- dispatch (action): 分发 action, 触发 reducer 调用,产生新的 state
- subscribe (listener): 注册监听,当产生了新的 state 时,自动调用
# 求和案例
# redux 精简版
-
redux 文件夹 ->store.js
// 该文件专门用于暴露一个store对象,整个应用只有一个store对象 //引入createStore,专门用于创建redux中最为核心的store对象 import { createStore } from "redux"; //引入为Count组件服务的reducer import countReducer from "./count_reducer"; //暴露store export default createStore(countReducer); -
redux 文件夹 ->count_reducer.js
/* 1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数 2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action) */ const initState = 0; //初始化状态 export default function countReducer(preState = initState, action) { // preState = initState形参的默认值 // 如果没传preState或者传递的preState是undefined,直接在这将preState的值改成0 //从action对象中获取:type、data const { type, data } = action; //根据type决定如何加工数据 switch (type) { case "increment": //如果是加 return preState + data; case "decrement": //如果是减 return preState - data; default: return preState; } } -
components 文件夹 ->Count 组件
import React, { Component } from "react"; //引入store,用于获取redux中保存状态 import store from "../../redux/store"; export default class Count extends Component { state = { carName: "奔驰c63" }; /* componentDidMount(){ //用于订阅redux中的状态的 //检测redux中状态的变化,只要变化,就调用render store.subscribe(()=>{ this.setState({}) }) } */ //加法 increment = () => { const { value } = this.selectNumber; // 告诉redux应该做什么 store.dispatch({ type: "increment", data: value * 1 }); }; //减法 decrement = () => { const { value } = this.selectNumber; store.dispatch({ type: "decrement", data: value * 1 }); }; //奇数再加 incrementIfOdd = () => { const { value } = this.selectNumber; // 专门获取状态的API const count = store.getState(); if (count % 2 !== 0) { store.dispatch({ type: "increment", data: value * 1 }); } }; //异步加 incrementAsync = () => { const { value } = this.selectNumber; setTimeout(() => { store.dispatch({ type: "increment", data: value * 1 }); }, 500); }; render() { return ( <div> <h1>当前求和为:{store.getState()}</h1> <select ref={(c) => (this.selectNumber = c)}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button> <button onClick={this.incrementAsync}>异步加</button> </div> ); } } -
src 文件夹 ->index.js
import React from "react"; import ReactDOM from "react-dom"; import App from "./App"; import store from "./redux/store"; ReactDOM.render(<App />, document.getElementById("root")); // 把整个APP都重新调了下render,APP调了render,那么APP里边所有组件都需要重新调一下render // 检测redux中状态的变化,只要变化,就调用render store.subscribe(() => { ReactDOM.render(<App />, document.getElementById("root")); }); -
(1).store.js: - 引入redux中的createStore函数,创建一个store - createStore调用时要传入一个为其服务的reducer 3).记得暴露store对象 (2).count_reducer.js: - reducer的本质是一个函数,接收:preState,action,返回加工后的状态 - reducer有两个作用:初始化状态,加工状态 - reducer被第一次调用时,是store自动触发的, 传递的preState是undefined, 传递的action是:{type:'xxx'} (3).在index.js中监测store中状态的改变,一旦发生改变重新渲染<App/> 备注:redux只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写。
# redux 完整版
-
新增的文件:redux 文件夹 ->constant.js
/* 该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错 */ export const INCREMENT = "increment"; export const DECREMENT = "decrement"; -
新增的文件:redux 文件夹 ->count_action.js
/* 该文件专门为Count组件生成action对象 */ import { INCREMENT, DECREMENT } from "./constant"; export const createIncrementAction = (data) => ({ type: INCREMENT, data }); export const createDecrementAction = (data) => ({ type: DECREMENT, data }); -
redux 文件夹 ->count_reducer.js
import { INCREMENT, DECREMENT } from "./constant"; const initState = 0; //初始化状态 export default function countReducer(preState = initState, action) { const { type, data } = action; //根据type决定如何加工数据 switch (type) { case INCREMENT: //如果是加 return preState + data; case DECREMENT: //若果是减 return preState - data; default: return preState; } } -
components 文件夹 ->Count 组件
//只修改了引入路径,其他地方没变,只需要将按钮点击时候的调用的函数名改一下即可 //引入actionCreator,专门用于创建action对象 import { createIncrementAction, createDecrementAction, } from "../../redux/count_action"; -
README.js
1.count_action.js 专门用于创建action对象 2.constant.js 放置容易写错的type值
# 异步 action 版
-
components 文件夹 ->Count 组件
import React, { Component } from "react"; //引入store,用于获取redux中保存状态 import store from "../../redux/store"; //引入actionCreator,专门用于创建action对象 import { createIncrementAction, createDecrementAction, createIncrementAsyncAction, } from "../../redux/count_action"; export default class Count extends Component { state = { carName: "奔驰c63" }; //异步加 incrementAsync = () => { const { value } = this.selectNumber; store.dispatch(createIncrementAsyncAction(value * 1, 500)); }; render() { return ( <div> <h1>当前求和为:{store.getState()}</h1> <select ref={(c) => (this.selectNumber = c)}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button onClick={this.incrementAsync}>异步加</button> </div> ); } } -
redux 文件夹 ->count_action.js
import { INCREMENT, DECREMENT } from "./constant"; //同步action,就是指action的值为Object类型的一般对象 export const createIncrementAction = (data) => ({ type: INCREMENT, data }); export const createDecrementAction = (data) => ({ type: DECREMENT, data }); //异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的。 export const createIncrementAsyncAction = (data, time) => { return (dispatch) => { setTimeout(() => { // 通知redux对data进行操作 // 这里是调用同步action dispatch(createIncrementAction(data)); }, time); }; }; -
redux 文件夹 ->store.js
/* 该文件专门用于暴露一个store对象,整个应用只有一个store对象 */ //引入createStore,专门用于创建redux中最为核心的store对象 import { createStore, applyMiddleware } from "redux"; //引入为Count组件服务的reducer import countReducer from "./count_reducer"; //引入redux-thunk,用于支持异步action import thunk from "redux-thunk"; //暴露store // applyMiddleware是用来执行中间件的 export default createStore(countReducer, applyMiddleware(thunk)); -
README.js
(1).明确:延迟的动作不想交给组件自身,想交给action (2).何时需要异步action:想要对状态进行操作,但是具体的数据靠异步任务返回。 (3).具体编码: - npm install --save redux-thunk,并配置在store中 - 创建action的函数不再返回一般对象,而是一个函数,该函数中写异步任务。 - 异步任务有结果后,分发一个同步的action去真正操作数据。 (4).备注:异步action不是必须要写的,完全可以自己等待异步任务的结果了再去分发同步action。
# React-Redux
npm i react-redux
# React-Redux 模型图
# React-Redux 的基本使用
-
App.jsx
import React, { Component } from "react"; import Count from "./containers/Count"; import store from "./redux/store"; export default class App extends Component { render() { return ( <div> {/* 给容器组件传递store */} {/* store不能在容器组件中亲自引入,必须通过props传递 */} <Count store={store} /> </div> ); } } -
index.js
import React from "react"; import ReactDOM from "react-dom"; import App from "./App"; import store from "./redux/store"; ReactDOM.render(<App />, document.getElementById("root")); //监测redux中状态的改变,如redux的状态发生了改变,那么重新渲染App组件 store.subscribe(() => { ReactDOM.render(<App />, document.getElementById("root")); }); -
containers 文件夹下 ->Count 文件夹 (容器组件)
//引入Count的UI组件 import CountUI from "../../components/Count"; //引入action import { createIncrementAction, createDecrementAction, createIncrementAsyncAction, } from "../../redux/count_action"; //从react-redux中引入connect用于连接UI组件与redux import { connect } from "react-redux"; /* 1.mapStateToProps函数返回的是一个对象; 2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value 3.mapStateToProps用于传递状态 */ function mapStateToProps(state) { return { count: state }; } /* 1.mapDispatchToProps函数返回的是一个对象; 2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value 3.mapDispatchToProps用于传递操作状态的方法 */ function mapDispatchToProps(dispatch) { return { //通知redux做对应的事情 jia: (number) => dispatch(createIncrementAction(number)), jian: (number) => dispatch(createDecrementAction(number)), jiaAsync: (number, time) => dispatch(createIncrementAsyncAction(number, time)), }; } //使用connect()()创建并暴露一个Count的容器组件 //connect的第一个参数里边有两个参数,这两个参数必须是函数 //第一个参数是:redux中所保存的状态 //第二个参数是:用于操作状态的方法 //connect的第二个参数是建立连接的UI组件 export default connect(mapStateToProps, mapDispatchToProps)(CountUI); -
components 文件夹下 - Count 文件夹 (UI 组件)
import React, { Component } from "react"; export default class Count extends Component { state = { carName: "奔驰c63" }; //加法 increment = () => { const { value } = this.selectNumber; this.props.jia(value * 1); }; //减法 decrement = () => { const { value } = this.selectNumber; this.props.jian(value * 1); }; //奇数再加 incrementIfOdd = () => { const { value } = this.selectNumber; if (this.props.count % 2 !== 0) { this.props.jia(value * 1); } }; //异步加 incrementAsync = () => { const { value } = this.selectNumber; this.props.jiaAsync(value * 1, 500); }; render() { return ( <div> <h1>当前求和为:{this.props.count}</h1> <select ref={(c) => (this.selectNumber = c)}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button> <button onClick={this.incrementAsync}>异步加</button> </div> ); } }
# 总结
- 明确两个概念:
- UI 组件:不能使用任何 redux 的 api,只负责页面的呈现、交互等。
- 容器组件:负责和 redux 通信,将结果交给 UI 组件。
- 如何创建一个容器组件 ———— 靠 react-redux 的 connect 函数
connect (mapStateToProps,mapDispatchToProps)(UI 组件)- mapStateToProps: 映射状态,返回值是一个对象
- mapDispatchToProps: 映射操作状态的方法,返回值是一个对象
- 备注 1:容器组件中的 store 是靠 props 传进去的,而不是在容器组件中直接引入
- 备注 2:mapDispatchToProps,可以是一个函数,也可以是一个对象 (一般写成对象类型)
# 基本使用中的优化写法 1:简写 mapDispatch
-
containers 文件夹下 ->Count 文件夹 (容器组件)
import React, { Component } from "react"; //引入action import { createIncrementAction, createDecrementAction, createIncrementAsyncAction, } from "../../redux/count_action"; //引入connect用于连接UI组件与redux import { connect } from "react-redux"; //定义UI组件 class Count extends Component { // render上边的内容和之前一样 render() { return ( <div> <h1>当前求和为:{this.props.count}</h1> </div> ); } } //使用connect()()创建并暴露一个Count的容器组件 export default connect( (state) => ({ count: state }), //mapDispatchToProps的一般写法 /* dispatch => ({ jia:number => dispatch(createIncrementAction(number)), jian:number => dispatch(createDecrementAction(number)), jiaAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)), }) */ //mapDispatchToProps的简写 //只需要将action提供过去,react-redux会自动做一个分发的动作 { jia: createIncrementAction, jian: createDecrementAction, jiaAsync: createIncrementAsyncAction, } )(Count);
# 基本使用中的优化写法 2:Provider 组件的使用
容器组件默认拥有了监测 redux 里边状态改变的能力,不用在 index.js 中使用 store.subscribe 去监测了 (react-redux 只要你用了,就不用自己去监测 redux 状态的变化,因为容器组件已经有了监测的能力。react-redux 的核心就是 connect ()() 方法)
-
App.jsx
import React, { Component } from "react"; import Count from "./containers/Count"; export default class App extends Component { render() { return ( <div> <Count /> </div> ); } } -
index.js
import React from "react"; import ReactDOM from "react-dom"; import App from "./App"; import store from "./redux/store"; import { Provider } from "react-redux"; ReactDOM.render( //把所有容器组件都需要的store交给Provider,然后Provider会把store精准的传递给需要store的容器组件 //目的是让APP所有的后代容器组件都能接收到store <Provider store={store}> <App /> </Provider>, document.getElementById("root") );
# 基本使用中的优化写法 3:整合 UI 组件和容器组件
-
containers 文件夹下 ->Count 文件夹
import React, { Component } from "react"; //引入action import { createIncrementAction, createDecrementAction, createIncrementAsyncAction, } from "../../redux/count_action"; //引入connect用于连接UI组件与redux import { connect } from "react-redux"; //定义UI组件 class Count extends Component { // 此处要和之前的内容保持一致 render() { return ( <div> <h1>当前求和为:{this.props.count}</h1> <select ref={(c) => (this.selectNumber = c)}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button> <button onClick={this.incrementAsync}>异步加</button> </div> ); } } //使用connect()()创建并暴露一个Count的容器组件 export default connect( (state) => ({ count: state }), //mapDispatchToProps的简写 { jia: createIncrementAction, jian: createDecrementAction, jiaAsync: createIncrementAsyncAction, } // 此处将UI组件和容器组件写在了一起 )(Count);
# 优化总结
- 容器组件和 UI 组件整合一个文件
- 无需自己给容器组件传递 store,给
包裹一个 即可。 - 使用了 react-redux 后也不用再自己检测 redux 中状态的改变了,容器组件可以自动完成这个工作。
- mapDispatchToProps 也可以简单的写成一个对象
- 一个组件要和 redux “打交道” 要经过哪几步?
- 定义好 UI 组件 — 不暴露
- 引入 connect 生成一个容器组件,并暴露,写法如下:
connect( state => ({key:value}), //映射状态 {key:xxxxxAction} //映射操作状态的方法 )(UI 组件) - 在 UI 组件中通过 this.props.xxxxxxx 读取和操作状态
# 案例 2:数据共享版
-
redux 文件夹 ->store.js
/* 该文件专门用于暴露一个store对象,整个应用只有一个store对象 */ //引入createStore,专门用于创建redux中最为核心的store对象 import { createStore, applyMiddleware, combineReducers } from "redux"; //引入为Count组件服务的reducer import countReducer from "./reducers/count"; //引入为Count组件服务的reducer import personReducer from "./reducers/person"; //引入redux-thunk,用于支持异步action import thunk from "redux-thunk"; //combineReducers调用的时候传入的那个对象就是redux保存的总状态对象 //combineReducers:汇总所有的reducer变为一个总的reducer const allReducer = combineReducers({ he: countReducer, rens: personReducer, }); //暴露store export default createStore(allReducer, applyMiddleware(thunk)); -
redux 文件夹 ->constant.js
/* 该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错 */ export const INCREMENT = "increment"; export const DECREMENT = "decrement"; export const ADD_PERSON = "add_person"; -
Count.jsx
// Count组件相比于之前的案例,本案例只在引入action这里做了修改,其余都一样 import { createIncrementAction, createDecrementAction, createIncrementAsyncAction, } from "../../redux/actions/count"; // 这里也做了修改 export default connect( (state) => ({ count: state.he, renshu: state.rens.length, }), { jia: createIncrementAction, jian: createDecrementAction, jiaAsync: createIncrementAsyncAction, } )(Count); -
redux 文件夹 ->actios 文件夹 ->count.js
// 本案例相比于之前的案例,只修改了constant的引入位置,其余的没有变化 import { INCREMENT, DECREMENT } from "../constant"; -
redux 文件夹 ->reducers 文件夹 ->count.js
// 本案例相比于之前的案例,只修改了constant的引入位置,其余的没有变化 import { INCREMENT, DECREMENT } from "../constant"; -
redux 文件夹 ->actios 文件夹 ->person.js
import { ADD_PERSON } from "../constant"; //创建增加一个人的action动作对象 export const createAddPersonAction = (personObj) => ({ type: ADD_PERSON, data: personObj, }); -
redux 文件夹 ->reducers 文件夹 ->person.js
import { ADD_PERSON } from "../constant"; //初始化人的列表 const initState = [{ id: "001", name: "tom", age: 18 }]; export default function personReducer(preState = initState, action) { const { type, data } = action; switch (type) { case ADD_PERSON: //若是添加一个人 return [data, ...preState]; default: return preState; } } -
Person.jsx
import React, { Component } from "react"; import { nanoid } from "nanoid"; import { connect } from "react-redux"; import { createAddPersonAction } from "../../redux/actions/person"; class Person extends Component { addPerson = () => { const name = this.nameNode.value; const age = this.ageNode.value; const personObj = { id: nanoid(), name, age }; this.props.jiaYiRen(personObj); this.nameNode.value = ""; this.ageNode.value = ""; }; render() { return ( <div> <h2>我是Person组件,上方组件求和为{this.props.he}</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.yiduiren.map((p) => { return ( <li key={p.id}> {p.name}--{p.age} </li> ); })} </ul> </div> ); } } export default connect( (state) => ({ yiduiren: state.rens, he: state.he }), //映射状态 { jiaYiRen: createAddPersonAction } //映射操作状态的方法 )(Person);
# 总结
- 定义一个 Pserson 组件,和 Count 组件通过 redux 共享数据。
- 为 Person 组件编写:reducer、action,配置 constant 常量。
- 重点:Person 的 reducer 和 Count 的 Reducer 要使用 combineReducers 进行合并,合并后的总状态是一个对象!!!
- 交给 store 的是总 reducer,最后注意在组件中取出状态的时候,记得 “取到位”。
# 纯函数和高阶函数
# 纯函数
-
一类特别的函数:只要是同样的输入 (实参),必定得到同样的输出 (返回),比如:demo (1) =>1,demo (20)=>20, 但如果 demo (12)=>99 这个就不是,因为入参和输出的数字不一样
-
必须遵守以下一些约束
- 不得改写参数数据
- 不会产生任何副作用,例如网络请求,输入和输出设备
- 不能调用 Date.now () 或者 Math.random () 等不纯的方法
-
redux 的 reducer 函数必须是一个纯函数
# 高阶函数
-
理解:一类特别的函数
- 情况 1: 参数是函数
- 情况 2: 返回是函数
-
常见的高阶函数:
- 定时器设置函数
- 数组的 forEach ()/map ()/filter ()/reduce ()/find ()/bind ()
- promise
- react-redux 中的 connect 函数
-
作用:能实现更加动态,更加可扩展的功能
# redux 开发者工具
-
安装 chrome 浏览器插件:
React Devtools -
下载工具依赖包:
npm install --save-dev redux-devtools-extension -
store.js
import { createStore, applyMiddleware, combineReducers } from "redux"; import countReducer from "./reducers/count"; import personReducer from "./reducers/person"; import thunk from "redux-thunk"; //引入redux-devtools-extension import { composeWithDevTools } from "redux-devtools-extension"; //汇总所有的reducer变为一个总的reducer const allReducer = combineReducers({ he: countReducer, rens: personReducer, }); //暴露store export default createStore( allReducer, composeWithDevTools(applyMiddleware(thunk)) );
# 总结
- yarn add redux-devtools-extension
- store 中进行配置
import {composeWithDevTools} from ‘redux-devtools-extension’
const store = createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))
# React 扩展
# 1. setState
# setState 更新状态的 2 种写法
-
对象式的 setState:setState (stateChange, [callback])
1.stateChange为状态改变对象(该对象可以体现出状态的更改) 2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用 state = {count:0} add = ()=>{ const {count} = this.state this.setState({count:count+1}) console.log(this.state.count) //结果是0(更新之前的状态),React状态更新是异步的。应该是输出完这句话之后,React再在页面上展示1(更新之后的状态) //如果是下面的写法,会输出1 this.setState({count:count+1},()=>{ console.log(this.state.count) //输出1 }) } -
函数式的 setState:setState (updater, [callback])
1.updater为返回stateChange对象的函数。 2.updater可以接收到state和props。 4.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。 state = {count:0} add = ()=>{ //这里会接收到state和props的值,所以这里不用提前取state的值 this.setState((state,props)=>{ return {count:state.count+1} }) } -
总结
1.对象式的setState是函数式的setState的简写方式(语法糖) 2.使用原则: (1).如果新状态不依赖于原状态 ===> 使用对象方式 (2).如果新状态依赖于原状态 ===> 使用函数方式 (3).如果需要在setState()执行后获取最新的状态数据, 要在第二个callback函数中读取 (4).开发当中怎么舒服怎么来,不用纠结于1,2条
# 2. lazyLoad
# 路由组件的 lazyLoad
//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
const Login = lazy(()=>import('@/pages/Login'))
//2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面
<Suspense fallback={<h1>loading.....</h1>}>
<Switch>
<Route path="/xxx" component={Xxxx}/>
<Redirect to="/login"/>
</Switch>
</Suspense>
# 3. Hooks
# 1. React Hook/Hooks 是什么?
- Hook 是 React 16.8.0 版本增加的新特性 / 新语法
- 可以让你在函数组件中使用 state 以及其他的 React 特性
# 2. 三个常用的 Hook
- State Hook: React.useState()
- Effect Hook: React.useEffect()
- Ref Hook: React.useRef()
# 3. State Hook
-
State Hook 让函数组件也可以有 state 状态,并进行状态数据的读写操作
-
语法: const [xxx, setXxx] = React.useState (initValue)
-
useState () 说明:
参数:第一次初始化指定的值在内部作缓存
返回值:包含 2 个元素的数组,第 1 个为内部当前状态值,第 2 个为更新状态值的函数 -
setXxx () 2 种写法:
setXxx (newValue): 参数为非函数值,直接指定新的状态值,内部用其覆盖原来的状态值
setXxx (value => newValue): 参数为函数,接收原本的状态值,返回新的状态值,内部用其覆盖原来的状态值 -
案例
import React, { useState } from "react"; export default function Example() { const [count, setCount] = useState(0); return ( <div> <h1>you click {count} times</h1> // 这里是点击一次触发一次,如果不是箭头函数的话有可能会上来就执行 <button onClick={() => { setCount(count + 1); }} > click me </button> </div> ); }
# 4. Effect Hook
-
Effect Hook 可以让你在函数组件中执行副作用操作 (用于模拟类组件中的生命周期钩子)
-
React 中的副作用操作:
- 发 ajax 请求数据获取
- 设置订阅 / 启动定时器
- 手动更改真实 DOM
-
语法和说明:
useEffect(() => { // 在此可以执行任何带副作用操作 return () => { // 在组件卸载前执行 // 在此做一些收尾工作, 比如清除定时器/取消订阅等 }; }, [stateValue]); // 如果指定的是[]代表谁也不监测, 回调函数只会在第一次render()后执行 // stateValue代表要监测的state(可以监测多个state),只有当state更新的时候才会执行函数体 // 如果没写第二个参数,代表谁也监测 -
可以把 useEffect Hook 看做如下三个函数的组合
- componentDidMount()
- componentDidUpdate()
- componentWillUnmount()
# 5. Ref Hook
- Ref Hook 可以在函数组件中存储 / 查找组件内的标签或任意其它数据
- 语法: const refContainer = useRef ()
- 作用:保存标签对象,功能与 React.createRef () 一样
# 案例
import React from "react";
import ReactDOM from "react-dom";
function Demo() {
const [count, setCount] = React.useState(0);
const myRef = React.useRef();
React.useEffect(() => {
let timer = setInterval(() => {
setCount((count) => count + 1);
}, 1000);
// 这里返回的函数相当于componentWillUnmount生命周期钩子
return () => {
clearInterval(timer);
};
}, []);
//加的回调
function add() {
//setCount(count+1) //第一种写法
setCount((count) => count + 1);
}
//提示输入的回调
function show() {
alert(myRef.current.value);
}
//卸载组件的回调
function unmount() {
ReactDOM.unmountComponentAtNode(document.getElementById("root"));
}
return (
<div>
<input type="text" ref={myRef} />
<h2>当前求和为:{count}</h2>
<button onClick={add}>点我+1</button>
<button onClick={unmount}>卸载组件</button>
<button onClick={show}>点我提示数据</button>
</div>
);
}
export default Demo;
# 6. Context Hook
-
让父子组件传值更简单 (解决父子组件传值的问题)
在用类声明组件时,父子组件的传值是通过组件属性和 props 进行的,那现在使用方法 (Function) 来声明组件,已经没有了 constructor 构造函数也就没有了 props 的接收,那父子组件的传值就成了一个问题。React Hooks 为我们准备了 useContext。它可以帮助我们跨越组件层级直接传递变量,实现共享。需要注意的是 useContext 和 redux 的作用是不同的,一个解决的是组件之间值传递的问题,一个是应用中统一管理状态的问题,但通过和 useReducer 的配合使用,可以实现类似 Redux 的作用。 -
案例
import React, { useState, createContext, useContext } from "react"; // createContext 创建 context // useContext 接收上下文变量 //===关键代码 const CountContext = createContext(); function Counter() { const count = useContext(CountContext); //一句话就可以得到count return <h2>{count}</h2>; } function Example() { const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => { setCount(count + 1); }} > click me </button> {/*======关键代码 */} <CountContext.Provider value={count}> <Counter /> </CountContext.Provider> </div> ); } export default Example;
# 7.useReducer 和 useContext 联合使用实现 redux
# 8. useMemo
useMemo 主要用来解决使用 React hooks 产生的无用渲染的性能问题。使用 function 的形式来声明组件,失去了 shouldCompnentUpdate(在组件更新之前)这个生命周期,也就是说我们没有办法通过组件更新前条件来决定组件是否更新。而且在函数组件中,也不再区分 mount 和 update 两个状态,这意味着函数组件的每一次调用都会执行内部的所有逻辑,就带来了非常大的性能损耗。useMemo 和 useCallback 都是解决上述性能问题的。
useMemo 缓存的是结果 (state),useCallback 缓存的是函数
# 案例
import React, { useState, useMemo } from "react";
function Example() {
const [xiaohong, setXiaohong] = useState("小红待客状态");
const [zhiling, setZhiling] = useState("志玲待客状态");
return (
<>
<button
onClick={() => {
setXiaohong(new Date().getTime());
}}
>
小红
</button>
<button
onClick={() => {
setZhiling(new Date().getTime() + ",志玲向我们走来了");
}}
>
志玲
</button>
<ChildComponent name={xiaohong}>{zhiling}</ChildComponent>
</>
);
}
function ChildComponent({ name, children }) {
function changeXiaohong(name) {
console.log("她来了,她来了。小红向我们走来了");
return name + ",小红向我们走来了";
}
const actionXiaohong = useMemo(() => changeXiaohong(name), [name]);
return (
<>
<div>{actionXiaohong}</div>
<div>{children}</div>
</>
);
}
export default Example;
# 9. 自定义 Hooks
其实自定义 Hooks 函数和用 Hooks 创建组件很相似,跟我们平时用 JavaScript 写函数几乎一模一样,可能就是多了些 React Hooks 的特性,自定义 Hooks 函数偏向于功能,而组件偏向于界面和业务逻辑。由于差别不大,所以使用起来也是很随意的。如果是小型项目是可以的,但是如果项目足够复杂,这会让项目结构不够清晰。所以学习自定义 Hooks 函数还是很有必要的。
# 案例
function useWinSize() {
const [size, setSize] = useState({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
});
const onResize = useCallback(() => {
setSize({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
});
}, []);
useEffect(() => {
window.addEventListener("resize", onResize);
return () => {
window.removeEventListener("resize", onResize);
};
}, []);
return size;
}
function Example() {
const size = useWinSize();
return (
<div>
页面Size:{size.width}x{size.height}
</div>
);
}
export default Example;
# 4. Fragment
# 使用
import React, { Component,Fragment } from 'react'
export default class Demo extends Component {
render() {
return (
<Fragment key={1}>
<input type="text"/>
<input type="text"/>
</Fragment>
)
}
}
<Fragment><Fragment>或者 <></>(空标签),但是Fragment允许在遍历的时候写key值,但是空标签不允许写任何值
# 作用
可以不用必须有一个真实的 DOM 根标签了
# 5. Context
# 理解
一种组件间通信方式,常用于【祖组件】与【后代组件】间通信,也就是爷孙辈以及爷孙之后的组件之间的通信
# 使用
1) 创建Context容器对象:
const XxxContext = React.createContext()
2) 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
<xxxContext.Provider value={数据}>
子组件
</xxxContext.Provider>
3) 后代组件读取数据:
//第一种方式:仅适用于类组件
static contextType = xxxContext // 声明接收context
this.context // 读取context中的value数据
//第二种方式: 函数组件与类组件都可以
<xxxContext.Consumer>
{
value => ( // value就是context中的value数据
要显示的内容
)
}
</xxxContext.Consumer>
# 案例
import React, { Component } from "react";
import "./index.css";
//创建Context对象
const MyContext = React.createContext();
const { Provider, Consumer } = MyContext;
export default class A extends Component {
state = { username: "tom", age: 18 };
render() {
const { username, age } = this.state;
return (
<div className="parent">
<h3>我是A组件</h3>
<h4>我的用户名是:{username}</h4>
<Provider value={{ username, age }}>
<B />
</Provider>
</div>
);
}
}
class B extends Component {
render() {
return (
<div className="child">
<h3>我是B组件</h3>
<C />
</div>
);
}
}
/* class C extends Component {
//声明接收context
static contextType = MyContext
render() {
const {username,age} = this.context
return (
<div className="grand">
<h3>我是C组件</h3>
<h4>我从A组件接收到的用户名:{username},年龄是{age}</h4>
</div>
)
}
} */
function C() {
return (
<div className="grand">
<h3>我是C组件</h3>
<h4>
我从A组件接收到的用户名:
<Consumer>{(value) => `${value.username},年龄是${value.age}`}</Consumer>
</h4>
</div>
);
}
# 注意
在应用开发中一般不用context, 一般都用它的封装react插件
# 6. 组件优化
# Component 的 2 个问题
-
只要执行 setState (), 即使不改变状态数据,组件也会重新 render () ==> 效率低
-
只当前组件重新 render (), 就会自动重新 render 子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低
# 效率高的做法
只有当组件的 state 或 props 数据发生改变时才重新 render ()
# 原因
Component 中的 shouldComponentUpdate () 总是返回 true
# 解决
-
办法 1:
- 重写 shouldComponentUpdate () 方法
- 比较新旧 state 或 props 数据,如果有变化才返回 true, 如果没有返回 false
-
办法 2:
-
使用 PureComponent:PureComponent 重写了 shouldComponentUpdate (), 只有 state 或 props 数据有变化才返回 true
-
注意:
只是进行 state 和 props 数据的浅比较,如果只是数据对象内部的数据变了,返回 false (如果还和之前的对象一样,返回 false)。不要直接修改 state 数据,而是要产生新数据,这样才可以触发state = { car: "奔驰", stus: ["a", "b"] }; // 这是对的 this.setState({ car: "奥迪" }); const { stus } = this.state; this.setState({ stus: ["c", ...stus] }); //产生了新数据 // 这是错误的 const obj = this.state; obj.car = "奥迪"; console.log(obj === this.state); // true this.setState(obj);项目中一般使用 PureComponent 来优化
-
# 7. render props
# 如何向组件内部动态传入带内容的结构 (标签)?
- Vue 中:
使用 slot 技术,也就是通过组件标签体传入结构 - React 中:
使用 children props: 通过组件标签体传入结构
使用 render props: 通过组件标签属性传入结构,而且可以携带数据,一般用 render 函数属性
# children props
<A>
<B>xxxx</B>
</A>;
{
this.props.children;
}
问题:如果 B 组件需要 A 组件内的数据,==> 做不到
# render props
<A render={(data) => <C data={data}></C>}></A>
// 这里的data是A组件中传递的state
A 组件: {this.props.render (内部 state 数据)}
C 组件:读取 A 组件传入的数据显示
# 8. 错误边界
# 理解:
错误边界 (Error boundary):用来捕获后代组件错误,渲染出备用页面
# 特点:
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
# 使用方式:
getDerivedStateFromError 配合 componentDidCatch
// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
console.log(error);
// 在render之前触发
// 返回新的state
return {
hasError: true,
};
}
componentDidCatch(error, info) {
// 统计页面的错误。发送请求发送到后台去
console.log(error, info);
}
# 9. 组件通信方式总结
# 组件间的关系:
- 父子组件
- 兄弟组件(非嵌套组件)
- 祖孙组件(跨级组件)
# 几种通信方式:
- props:
- children props
- render props
- 消息订阅 - 发布:
pubs-sub、event 等等 - 集中式管理:
redux、dva 等等 - conText:
生产者 - 消费者模式
# 比较好的搭配方式:
- 父子组件:props
- 兄弟组件:消息订阅 - 发布、集中式管理
- 祖孙组件 (跨级组件):消息订阅 - 发布、集中式管理、conText (开发用的少,封装插件用的多)