}
```
之后将UI组件中所有关于原本的 `redux` 的操作删除掉即可,重新启动项目,页面正常显示

现在我们就得到了一个容器组件。
## 容器组件给UI组件传递props
修改容器组件 `src\container\Count.jsx`
容器组件中的 `connect` 方法第一次调用可以传递两个参数,两个参数都是一个方法
```js
// 引入UI组件
import Count from "../pages/Count"
// 引入连接UI组件和store的方法
import { connect } from "react-redux"
// 引入用来操作对象的action
import { addAction, subtractAction, addAsyncAction } from "../redux/count_action"
/**
* 1.mapStateToProps 返回的是一个普通对象
* 2.返回对象的key作为props的key传递给UI组件,value作为props的value传递给UI组件
* 3.接收到的state是redux中最新的状态
* @param {Object} state
*/
function mapStateToProps(state) {
return {
count: state
}
}
/**
* 1.mapDispatchToProps 返回的是一个对象,对象的属性值必须是方法
* 2.方法用来操作redux中的状态
* 3.mapDispatchToProps 用于传递操作状态的方法
* @param {Function} dispatch
* @returns Object
*/
function mapDispatchToProps(dispatch) {
return {
addAction: (value) => dispatch(addAction(value)),
subtractAction: (value) => dispatch(subtractAction(value)),
addAsyncAction: (value, time) => dispatch(addAsyncAction(value, time))
}
}
// 使用 connect()() 创建并暴露一个 Count 的容器组件
export default connect(mapStateToProps, mapDispatchToProps)(Count)
```
然后再UI组件中通过 `props` 获取从容器组件传递过来的值
```js
addIfAsync = () => {
const { selectVal } = this.state
this.props.addAsyncAction(selectVal, 1000)
}
```
修改完成后效果一致。
## mapDispatchToProps 简写
可以写成下面这种形成
```js
/**
* mapDispatchToProps 简写形成
* 我们只需要写成对象,对象的属性值是 action 方法
* react-redux 会自动帮我们完成分发更新状态
*/
const mapDispatchToProps = {
addAction,
subtractAction,
addAsyncAction
}
// 使用 connect()() 创建并暴露一个 Count 的容器组件
export default connect(mapStateToProps, mapDispatchToProps)(Count)
```
## 状态改变自动渲染页面
使用了 `react-redux` 后,在 `index.js` 中添加的监听状态改变的方法就不需要写了
```js
- import store from "./redux/store"
// 监听redux中状态的改变,只要状态发生了改变就会触发render方法重新渲染页面
- store.subscribe(() => {
- ReactDOM.render(
-
-
- ,
- document.getElementById("root")
- );
- })
```
我们改变 redux 中的状态后页面仍然会刷新,这是因为在 connect 方法中替我们完成了页面监听的功能。
## Provider 组件使用
之前我们需要在容器组件上一个个的添加 store 参数,如果容器组件多的话,就需要写多个 `store={store}` 的props参数传递
有了 `Provider` 组件后我们就不要再容器组件上一个一个添加 store 了,它会自动帮我们分析全局的容器组件,我们只需要传一次 store 参数即可
修改 `src/index.js`
```jsx
import { Provider } from "react-redux"
import store from "./redux/store"
ReactDOM.render(
,
document.getElementById("root")
);
```
把我们原来在容器组件上添加的 store 参数去掉,查看效果和之前一致。
## 将容器组件和UI组件二合一
上面的写法中我们将容器组件和UI组件分成了两个文件去写,这样当我们的组件比较多的时候文件定义就会比较乱,所以我们可以通过如下写法,将两个组件合成一个组件
```jsx
import React, { Component } from 'react'
import { Button, Typography } from 'antd';
import { connect } from "react-redux"
import {addAction} from "../redux/count_action"
const { Title } = Typography;
const mapState = state => {
return {
count: state
}
}
const mapDispatch = {
addAction
}
class ExerciseCount extends Component {
addCount = () => {
this.props.addAction(1)
}
render() {
return (
当前的和为:{this.props.count}
)
}
}
export default connect(mapState, mapDispatch)(ExerciseCount)
```
## combineReducers 方法合并多个 reducer
之前我们在 store 中只添加了一个 reduce,当我们多个组件都需要用到 redux 时,无法同时传递多个,我们可以借助 combineReducers 方法来实现在 store 中同时连接多个 reducer
修改 `store.js`
```js
// 引入redux
import { createStore, applyMiddleware, combineReducers } from 'redux'
// 引入count的reduces方法
import count_reduce from './reduces/count'
// 引入person的reduces方法
import person_reduce from './reduces/person'
// 引入使用异步redux的库
import thunk from "redux-thunk"
// 使用 combineReducers 方法合并多个组件的 reduc 方法
// 这个对象是 redux 中存储的对象,通过 key value 的形式获取 redux 中的数据
const allReduces = combineReducers({
count: count_reduce,
persons: person_reduce
})
export default createStore(allReduces, applyMiddleware(thunk))
```
从 `redux` 增加 `combineReducers` 方法导出,然后调用 `combineReducers,传递一个` `reducer` 对象作为参数,这个方法的返回值作为 `createStore` 方法的第一个参数。另外给 `combineReducers` 方法传递的对象就会作为 `redux` 中保存数据的 `kye value`。因此我们在通过 `redux` 取值时也要修改为 `key value` 的形式来取值。
## 纯函数
- 一类特别的函数,只要输入同样的参数,必定会得到同样的输出
- 必须遵守以下的约定
- 不得改写参数数据
- 不会产生副作用,例如网络请求,输入和输出等
- 不能调用如 Date.now() 或者 Math.random() 等不纯的方法
- redux 的 reducer 函数必须是一个纯函数
## redux 开发者工具
首先在浏览器搜索并安装插件

然后再我们的项目中安装
```shell
cnpm i redux-devtools-extension --save
```
修改 `store.js`
```js
// 引入 composeWithDevTools 方法
import { composeWithDevTools } from "redux-devtools-extension"
......省略其他代码
// 在最后的导出方法中的第二个参数修改为 composeWithDevTools() 方法
export default createStore(allReduces, composeWithDevTools(applyMiddleware(thunk)))
```
然后打开浏览器,打开控制台即可查看 redux 中存储的所有状态值

## 抽离 reduce 引入文件
当我们有个` reduce` 需要引入时,如果全部放在 `store.js` 文件中,这个文件会变得很难看,所以我们要单独写一个专门用于汇总 `reduce` 的文件
新建 `src\redux\reduces\index.js` 文件
```js
/**
* 这个文件用于汇总所有的reduce
*/
import count from "./count"
import persons from "./person"
// 使用 combineReducers 方法合并多个组件的 reduc 方法
import { combineReducers } from "redux"
export default combineReducers({
count,
persons
})
```
然后修改 `store.js` ,将原本引入的 `reduce `删除,替换为汇总完的文件即可。完整版的 `store.js` 代码如下
```js
// 引入redux,createStore 用于创建一个store,applyMiddleware用于中间件实现异步action
import { createStore, applyMiddleware } from 'redux'
// 引入汇总的reduce
import allReduces from "../redux/reduces"
// 引入redux开发者工具依赖的库
import { composeWithDevTools } from "redux-devtools-extension"
// 引入使用异步redux的库
import thunk from "redux-thunk"
export default createStore(allReduces, composeWithDevTools(applyMiddleware(thunk)))
```
# 第十六章 项目打包和预览
## 打包和预览
打包执行命令
```shell
npm run build
```
打包完毕后会提示我们执行如下命令

我们执行这个命令前需要全局安装 `serve`
```shell
npm i serve -g
```
`serve` 可以让我们的任意文件夹变成服务器的根目录,可以帮助我们实现打包后的项目的快速预览
打包完毕后的 -s 可以省略不输入,直接输入 `serve build` 即可查看项目

## 配置打包目录
默认打包出来我们需要吧打包文件放在服务器的根目录下使用,但是有时候我们需要吧文件放在二级目录下。这时我们需要修改 `package.json` 文件

再配置文件内添加 `homepage` 属性,声明二级菜单的名称即可
# 第十七章 React 扩展
## setState 的两种写法
1.对象式 setState
```js
/**
* 1.对象式setState
*/
// setState 方法接收第二个参数,当更新完毕后会触发回调
this.setState({ count: count + 1 },()=>{
console.log('更新后的的count:' + this.state.count);
})
```
2.函数式 setState
第一个传递的是一个返回值是状态改变的对象
```js
/**
* 2.函数式setState
*/
this.setState((state,props) => {
console.log(props);
return {
count: state.count + 1
}
}, () => {
console.log('更新后的的count:' + this.state.count);
})
```
## 路由懒加载
当我们需要显示某个组件时再去加载某个组件,以缓解第一次加载页面时的压力。
```js
// 从 react 中引入自带的 lazy 方法和 Suspense 组件
import React, { Component, lazy, Suspense } from 'react'
import { NavLink, Route, } from "react-router-dom"
import "./router.css"
import Loading from "./Loading"
// 修改路由组件的引入方式为下面的方法
const Home = lazy(() => import("./Home"))
const About = lazy(() => import("./About"))
export default class index extends Component {
render() {
return (
Home
About
{/* 使用 Suspense 组件报告路由组件,fallback 属性定义等待加载期间显示的组件 */}
}>
)
}
}
```
- 从 react 中引入自带的 lazy 方法和 Suspense 组件
- 修改路由组件的引入方式 `const Home = lazy(() => import("./Home"))`
- 使用 Suspense 组件报告路由组件,fallback 属性定义等待加载期间显示的组件 `fallback={`
这样修改后组件只会在需要时被加载,加载过的组件在第二次显示时不会二次加载

## stateHooks
```js
import react from "react"
function stateHooks(){
// 使用react.useState初始化一个值,这个方法会返回一个数组,数组第一位是要定义的值,第二个是修改这个值得函数
const [count,setCount] = react.useState(0)
const [name,setName] = react.useState('Tome')
// 点击加1按钮调用setCount方法,传入一个新的值来修改初始化的值
// 在第一次useState后回缓存count,点击修改会重新调用当前的函数,但是不会更改count的值
function add(){
// 修改函数的两种写法之一
setCount(count+1)
}
function changeName(){
/**
* 修改函数的两种写法之二
* 传入一个函数,这个函数会接受到之前的值,然后返回新的值覆盖之前的值
*/
setName(preName=>preName='Juar')
}
return (
当前的数据和为:{count}
我的名字是:{name}
)
}
export default stateHooks
```
`react.useState` 方法可以让我们在函数式组件中使用 stat,这个返回接收一个初始值,返回一个两位数的数组。数组第一位是这个初始化值的名称,第二位是修改这个值的方法。
在第一次useState后回缓存count,点击修改会重新调用当前的函数,但是不会更改count的值。
## useEffect
`useEffect ` 方法可以在函数式组件中模拟声明周期的一个钩子函数。它接收两个参数
```js
import react from "react"
import ReactDom from "react-dom"
function stateHooks() {
// 使用react.useState初始化一个值,这个方法会返回一个数组,数组第一位是要定义的值,第二个是修改这个值得函数
const [count, setCount] = react.useState(0)
const [name, setName] = react.useState('Tome')
/**
* react.useEffect 方法接收两个参数
* 1.函数
* 2.要监听的useState定义的stateValue,如果不传递第二个参数,则默认监听全部value
* 如果传入一个空数组,则不会监听任何stateValue值得改变
* 如果定义个某个stateValue,则只会监听这个值得变化
*/
react.useEffect(() => {
let timer = setInterval(() => {
setCount(count=>count+1)
}, 1000);
// 返回的方法可以当做 componentWillUnmount 钩子
return ()=>{
// 在卸载组件前清除定时器
clearInterval(timer)
}
}, [])
// 点击加1按钮调用setCount方法,传入一个新的值来修改初始化的值
// 在第一次useState后回缓存count,点击修改会重新调用当前的函数,但是不会更改count的值
function add() {
// 修改函数的两种写法之一
setCount(count + 1)
}
function changeName() {
/**
* 修改函数的两种写法之二
* 传入一个函数,这个函数会接受到之前的值,然后返回新的值覆盖之前的值
*/
setName(preName => preName = 'Juar')
}
function unmont(){
ReactDom.unmountComponentAtNode(document.getElementById('root'))
}
return (
当前的数据和为:{count}
我的名字是:{name}
)
}
export default stateHooks
```
`useEffect ` 方法的几种状态
- 只传入一个函数,则表示监听了所有 `stateValue` 值的变化,只要有值发生改变就会触发传入的函数。相当于 `componentDidUpdate` 函数
- 传递一个空的数组,不会监听任何值的改变,只会在组件第一次加载时触发。相当于 `componentDidMount`
- 当传入的函数返回一个函数时,会在组件销毁前出发,因此返回的函数相当于 `componentWillUnmount`
## useRef
`useRef` 可以让我们在函数式组件中使用 ref 获取输入框的值,用法于类式组件中的 `createRef` 一样
```js
import React from "react"
function Demo(){
const myInput = React.useRef()
function getInputVal(){
console.log(myInput.current.value);
}
return (
)
}
export default Demo
```
## Fragment
当我们不得不指定一个 div 标签的时候,可以使用 `Fragment` 来包裹页面元素,然后 react 在渲染的时候不会去渲染 `Fragment` 标签,从而来优化我们的页面结构
```js
import React,{Fragment} from "react"
function Demo(){
return (
)
}
export default Demo
```
渲染后的页面
```html
```
## createContext
在多级组件嵌套时我们可以通过 context 来跨级传递参数
```js
import React, { Component } from 'react'
import "./index.css"
// 创建 Context 容器对象
const myContext = React.createContext()
// 从 myContext 身上解构出 Provider 组件,包裹住父组件,使用 value 属性将数据传递个子组件
const { Provider, Consumer } = myContext
export default class A extends Component {
state = {
name: "Tome",
age: 10
}
render() {
const { name, age } = this.state
return (
我是A组件
我的名字是:{name},我的年龄是:{age}
)
}
}
class B extends Component {
render() {
return (
我是B组件
)
}
}
class C extends Component {
// 在C组件中接收上面定义的myContext,必须定义为 static contextType = myContext
static contextType = myContext
render() {
// 然后我们就可以使用 this.context 接收到根组件传递过来的值
console.log(this.context); //=> {name: 'Tome', age: 10}
const { name, age } = this.context
return (
我是C组件
我从A组件接收到的名字是:{name},年龄是:{age}
)
}
}
function D() {
return (
我是D组件
我从A组件接收到的名字是:
{/* 在函数式组件中的使用方法 */}
{
context => {
const { name, age } = context
return `${name},年龄是:${age}`
}
}
)
}
```
## PureComponent
我们做组件优化时,如果状态没有发生改变,则不应该重复调用 render 函数进行重复渲染。我们可以修改组件继承自 PureComponent,它在底层做了优化,只有状态在真正发生改变时才会触发 render 方法。
```js
import React, { PureComponent } from 'react'
import "./index.css"
export default class index extends PureComponent {
state = {
carName:'奔驰c63'
}
changeCar = ()=>{
this.setState({
carName:'迈巴赫'
})
}
render() {
// 如果组件继承Component组件,则不管有没有发生改变,都会重复调用render方法进行渲染
// 修改继承 PureComponent,则只有在state真正发生变化时才会触发render
console.log('更新触发');
return (
我的车是:{this.state.carName}
)
}
}
class B extends PureComponent{
render(){
console.log('子组件render');
return(
我是子组件
)
}
}
```
## propsRender
通过函数调用的形式,向组件内动态传入带内容的解构
```js
import React, { Component } from 'react'
import "./index.css"
export default class index extends Component {
render() {
return (
propsRender
{/* render 等于一个函数,返回一个组件,接收到参数传递给子组件 */}
}>
)
}
}
class A extends Component {
state = {
carName: "奔驰"
}
render() {
const { carName } = this.state
return (
我是A组件
我的汽车是:{carName}
{/* 在A组件中调用props上的render方法渲染返回的组件并传递参数 */}
{this.props.render(carName)}
)
}
}
class B extends Component {
render() {
return (
我是B组件
从A组件接收到的名字是:{this.props.carName}
)
}
}
```

## 错误边界
```js
import React, { Component } from 'react'
import Child from "./Child"
export default class Parent extends Component {
state = {
hasError: null
}
// 只能捕获后台组件生命周期上发生的错误,不能捕获自己组件产生的错误。同时其他组件非生命周期发生的错误,
// 例如:自定义的方法事件,计时器等错误不能被捕获
static getDerivedStateFromError(error) {
console.log('@@', error);
return {
hasError: true
}
}
// 当组件发生错误后回触发这个方法,可以在这个方法中记录错误次数
componentDidCatch(){
console.log('发生了错误,通知服务器');
}
render() {
return (
我的Parent组件
{/* 判断错误是否存在,来显示不同的组件。避免一个错误影响全局页面 */}
{this.state.hasError ? 网络异常,请稍后重试
: }
)
}
}
```
注意:该功能只能在生产环境下有效。本地开发环境不生效。
## 组件间通信方式总结
组件间的关系
- 父子组件
- 兄弟组件(非嵌套组件)
- 祖孙组件(跨级组件)
几种通信方式
- props
- children props
- render props
- 消息订阅、发布
- pubsub-js、event 等
- 集中式状态管理
- redux,dva 等
- conText
- 生产者消费者模式
比较好的搭配方式
- 父子组件:props
- 兄弟组件:消息订阅、集中式状态管理
- 祖孙组件:消息订阅、集中式状态管理、conText
# js相关复习
## 类的创建和继承
```js
// 创建一个类
class Person{
// 声明一个构造器,接受name和age两个属性
constructor(name,age){
this.name = name
this.age = age
}
// 设置speak方法
speak(){
// 类中方法的this指向的是类的实例化的对象
console.log(`我叫${this.name},几年${this.age}岁了`);
}
}
// 创建类的实例化对象p1
const p1 = new Person("张三",19)
// 调用类中的speak方法
p1.speak() //=> 我叫张三,几年19岁了
// 创建一个学生类继承上面的Person类
class Student extends Person{
// 给Studnet类添加一个构造器方法
constructor(name,age,grade){
// 如果继承的类写了constructor方法,则在constructor方法内部第一行添加super方法,用来调用父类的constructor方法
super(name,age)
// 添加Student类独有的属性
this.grade = grade
}
// 重写父类的speak方法
speak(){
console.log(`我叫${this.name},几年${this.age}岁了,今年上${this.grade}`);
}
}
const s1 = new Student('lisi','18','高一')
s1.speak() //=> 我叫lisi,几年18岁了,今年上高一
```
## 展开语法
```js
// 展开数组
let a = [1, 2, 3, 4, 5]
console.log(...a); //=> 1 2 3 4 5
// 合并两个数组
let b = [6, 7, 8, 9]
console.log([...a, ...b]); //=> [1, 2, 3, 4, 5, 6, 7, 8, 9]
// 方法接收不定数参数
function sum(...numbers) {
// 接收到的是一个数组
console.log(numbers);
return numbers.reduce((a, b) => {
return a + b
}, 0)
}
console.log(sum(1, 2, 3)); //=> 6
console.log(sum(3)); //=> 3
// 使用展开语法浅拷贝对象,这种方法只能复制对象中第一层的数据
let person = {
name: "Tom",
age: 19,
classInfo: {
height: 180
}
}
let person2 = {
...person
}
person.name = "jery"
person.classInfo.height = 190
console.log(person2); //=> {"name":"Tom","age":19,"classInfo":{"height":190}}
console.log(person); //=> {"name":"jery","age":19,"classInfo":{"height":190}}
```