react(下)

106 阅读46分钟

16 组件传值

正传---props

函数组件

子组件

// 1.把props当成函数的形参传入
let Zi=(props)=>{
    return (
        <div>
            {/* 2.使用props来进行数据的展示 */}
            ziziziziziziz--{props.title}
        </div>
    )
}
​
export default Zi

父组件进行传递

import Zi from "./zi.jsx"let Fu=()=>{
    return (
        <div>
            FFUFUFUFUFUFUF
            {/* 父组件传递 */}
            <Zi title="我是父组件的书"></Zi>
        </div>
    )
}
​
export default Fu

公司的写法

父组件
​
import Zi from "./zi.jsx"let Fu=()=>{
    // 定义数据
    let obj={
        title:"你好么么哒!!!!!"
    }
    return (
        <div>
            FFUFUFUFUFUFUF
            {/* 父组件使用扩展运符传递 */}
            <Zi {...obj}></Zi>
        </div>
    )
}
​
export default Fu
​
​
​
子组件
import React from 'react'// 1.形参传入props
export default function Zi(props) {
​
    let {title}=props
​
  return (
    <div>
        Zi
        <span>
            {title}
        </span>
      
    </div>
  )
}
​

类组件

1.子组件 this.props.xxx

import React, { Component } from 'react'
​
export default class zi extends Component {
  render() {
    return (
      <div>
          zi
          {/* 1.子组件定义props */}
          <h1>父组件的数据式-----{this.props.title}</h1>
      </div>
    )
  }
}
​

2.父组件传递

import React, { Component } from 'react'
import Zi from "./zi.jsx"
export default class fu extends Component {
  render() {
    return (
      <div>
          fu
          {/* 父组件给子组件传递数据 */}
        <Zi title="我式父组件的数据"></Zi>    
      </div>
    )
  }
}
​

公司写法

子组件中使用结构来优化代码

import React, { Component } from 'react'export default class zi extends Component {
  render() {
    //   使用解构的方式见到了this。props的重复出现率
    let {title,age,name,sex,love}=this.props
    return (
      <div>
          zi
          {/* 1.子组件定义props */}
          <h1>父组件的数据式-----{title}</h1>
          <h1>父组件的数据式-----{age}</h1>
          <h1>父组件的数据式-----{name}</h1>
          <h1>父组件的数据式-----{sex}</h1>
          <h1>父组件的数据式-----{love}</h1>
        
      </div>
    )
  }
}
​

父组件使用扩展运算符传递数据

import React, { Component } from 'react'
import Zi from "./zi.jsx"
export default class fu extends Component {
  render() {
      let obj={
          title:"我是title",
          name:"我是name",
          age:18,
          sex:"男",
          love:"女"
      }
    return (
      <div>
          fu
          {/* 扩展运算符快速传递数据 */}
        <Zi {...obj}></Zi>    
      </div>
    )
  }
}
​

this.props.children

思考

在react组件调用中我们的开标前和关标签中能否插入内容 ?

不能 因为组件是一个完整的独立的个体 默认不能插入

this.props.children 他表示所有组件的子节点(默认写上没有任何作用 在组件被调用的时候 如果我们在他的开关标签中插入dom元素 那么this.props.chilren 就会接收并且显示)

逆向传值--使用props接收一个函数

子组件把数据给父组件

利用回调函数 父组件提供函数,子组件调用并且把数据当做函数的参数传入

子组件

import React, { Component } from 'react'
​
export default class zi extends Component {
  render() {
    return (
      <div>
          zizizizzizi
          {/* 1.逆向传值必须通过事件来触发 一个父组件传递过来的函数*/}
          <button onClick={this.props.demofun}>点我进行逆向传值</button>
      </div>
    )
  }
}
​

父组件

import React, { Component } from 'react'
import Zi from "./zi.jsx"
export default class fu extends Component {
    fufun=()=>{
        
    }
  render() {
    return (
      <div>
          fuffufufufuf
          {/* 2.父组件给子组件传递一个函数 */}
          <Zi demofun={this.fufun}></Zi>
        
      </div>
    )
  }
}
​

子组件

import React, { Component } from 'react'
​
export default class zi extends Component {
  render() {
    return (
      <div>
          zizizizzizi
          {/* 1.逆向传值必须通过事件来触发 一个父组件传递过来的函数*/}
          {/* 3.给函数进行实参的传递 */}
          <button onClick={this.props.demofun.bind(this,"我是子组件的数据")}>点我进行逆向传值</button>
      </div>
    )
  }
}
​

父组件

import React, { Component } from 'react'
import Zi from "./zi.jsx"
export default class fu extends Component {
    // 4.父组件设置形参接收子组件绑定的实参
    fufun=(text)=>{
            console.log("我是父组件的函数",text)
    }
  render() {
    return (
      <div>
          fuffufufufuf
          {/* 2.父组件给子组件传递一个函数 */}
          <Zi demofun={this.fufun}></Zi>
        
      </div>
    )
  }
}
​

同胞传值

Pubsub-js

react中默认是不能进行同胞传值的 如果我们要进行 那么必须依赖 pubsub-js(是-js 千万不要记错了) 库来实现

1.npm install --save pubsub-js

2.抛出 在需要传递的组件中使用 Pubsub.publish(“自定义事件名”,"数据") publish创建自定义事件

import React, { Component } from 'react'
// 1.引用pubsub-js
import Pubsub from "pubsub-js"
export default class zia extends Component {
    fun=()=>{
        // 2.publish抛出自定义事件
        Pubsub.publish("zia","我是zia的数据么么哒!!!!!")
    }
  render() {
    return (
      <div>zia
          <button onClick={this.fun}>点我把数据传递到zib</button>
​
      </div>
    )
  }
}
​

3.接收 在需要接收数据的组件中使用Pubsub.subscribe("你监听的事件",()=>{})subscribe 监听自定义事件

import React, { Component } from 'react'
// 3.引用pubsub
import Pubsub from "pubsub-js"
export default class zib extends Component {
    constructor(){
        super()
        console.log("1.react初始化数据")
    }
    componentWillMount(){
        console.log("2.在渲染之前调用")
    }
    componentDidMount() { 
    //    接收   监听同胞传值的自定义事件
        // 回调函数的第一个形参是你监听的自定义事件的名字
        // 回调函数的第二个形参就是自定义事件上绑定的数据
    Pubsub.subscribe("zia",(a,b)=>{
        console.log(a)
        console.log(b)
    })
​
     }
  render() {
      console.log("3开始渲染")
    return (
      <div>zib</div>
    )
  }
}
​

状态提升--中间人模式

React中的状态提升概括来说,就是将多个组件需要共享的状态提升到它们最近的父组件

上.在父组件上改变这个状态然后通过props分发给子组件.

跨组件传值

context对象--上下文对象

react 组件间传递数据是通过 props 向下,是单向传递的,从父级一层一层地通过 props 地向下传递到子子孙孙,有的时候我们组件一层一层的嵌套多层,这样这种方式一层一层传递麻烦,如果想跃层传递,这就会用到 context

context:上下文对象

context很好的解决了跨组件传值的复杂度。可以快速的进行跨组件数据的传递。

想要使用context进行跨组件传值那么就要使用createContext()方法同时方法中给我们提供了两个对象:

Provider对象 生产者---->用来生产数据 Consumer对象 消费者---->用来使用数据

import React, { Component } from 'react'
// 1.创建context对象
const GlobalContext = React.createContext()
​
class Zia extends Component {
    render() {
        return (
            <div>
                我是第一个子组件
            </div>
        )
    }
}
class Zib extends Component {
    render() {
        return (
​
            <div>
                我是第2个子组件
                {/* 3.任意组件引入GlobalContext并调用context,使用GlobalContext.Consumer(消费者) */}
​
                <GlobalContext.Consumer>
                    {/* 4.在回调函数中直接使用生产者的数据 */}
                    {
​
                        (value) => {
                            return <h1>{value.name}</h1>
                        }
                    }
                </GlobalContext.Consumer>
            </div>
​
        )
    }
}
export default class fu extends Component {
    render() {
        return (
            // 2.在根组件件引入GlobalContext,并使用GlobalContext.Provider生产者
            // 并且使用value属性传入数据
            <GlobalContext.Provider value={{ name: "xixi", age: 18 }}>
                <div>
                    我是根组件
                    <Zia />
                    <Zib />
                </div>
            </GlobalContext.Provider>
        )
    }
}
​

redux

redux是什么?

redux就是一个javascript的状态管理工具 可以集中的管理react中多个组件的状态 让我们组件之间数据传递变得非常的简单

redux是一个第三方的 也就是说他可以在react中用 也可以在vue中进行使用

如果组件需要进行跨层级传值 传统的方式 就是组件一层层的进行逆向传值传递到他们最近的一个父组件身上 然后再一层层的进行正向传值

redux的三大原则

1.单一数据源 :整个项目的数据都被存储在一个store对象中

2.state是只读的:如果我们想改变state的数据 那么必须触发action里面的修改动作来执行修改

3.使用纯函数来进行修改:reducer就是一个函数 我们通过reducer中的state和action动作来进行数据的修改

redux使用

1.下载redux npm install --save redux

2.在项目的文件夹中创建一个store文件夹(容纳redux的代码)

3.在新建一个文件容纳基本代码

// 1.创建redux对象(先引用redux创建方法createStore)
import {createStore} from "redux"
// 6.创建创建state数据
let data={
    name:"xixi",
    age:18,
    sex:"男"
}
// 5.创建reducer 是一个方法  其中保存的就是redux中存储的变量和修改变量的方法
// state就是数据状态  
// action 修改上面数据状态的一些动作
// 7.把上面的数据传递给state
let reducer=(state=data,action)=>{
    // 8把state return
    return state
}
// 2.开始创建redux对象
// 4.把状态和修改状态的方法传递到初始化redux中
let store=createStore(reducer)
// 3.暴露redux对象
export default store
读取redux中的数据

store.getState().你要读取的数据

import React, { Component } from 'react'
// 1.引用redux
import store from "../store/index.js"
export default class demoa extends Component {
    // 2.把数据复制给组件的state中进行保存
    state={
        name:store.getState().name
    }
​
  render() {
    return (
      <div>
          <h1>redux的基本使用</h1>
          {/* 3.读取 */}
          <h1>使用数据---{this.state.name}</h1>
      </div>
    )
  }
}
​
基本数据修改

我们需要通过dispatch()来调用写在action中的修改动作

千万不要和vuex搞混了 因为在vuex中 dispatch触发action是进行异步操纵的触发器

但是但是 在redux中 dispatch触发 的这个action里面存储的是修改状态的动作

  fun=()=>{
        // 通过dispatch来修改数据
        // store.dispach({type:"你要触发的修改动作"})
        store.dispach({type:"USER_UP_NAME"})
    }

编写修改数据的动作

import {createStore} from "redux"let data={
    name:"xixi",
    age:18,
    sex:"男"
}
​
let reducer=(state=data,action)=>{
    // 创建修改动作
    switch (action.type) {
        case "USER_UP_NAME":
            console.log({...state,name:"我变了"})
            return {...state,name:"我变了"}
            break;
    
        default:
            return state
            break;
    }
}
let store=createStore(reducer)
​
export default store

但是大家运行后发现 数据修改了 但是页面并没有发生改变

原因很简单 因为 虽然你数据变了 但是组件中的render并没有重新执行 那么页面当然不会修改了

subscribe() 监听redux state的状态 改变就会触发

import React, { Component } from 'react'
// 1.引用redux
import store from "../store/index.js"
export default class demoa extends Component {
    // 2.把数据复制给组件的state中进行保存
    state={
        name:store.getState().name
    }
​
    // 我们可以监控这redux中的state数据  如果redux中的数据改变了
    // 我重启读取下并且在触发组件中的render那么 页面的内容就会改变
    componentDidMount() { 
        
        store.subscribe(()=>{
            // 当redux中的state数据改变了 那么subscribe就会触发
            this.setState({
                name:store.getState().name
            })
        })
​
     }
​
    fun=()=>{
        // 通过dispatch来修改数据
        // store.dispach({type:"你要触发的修改动作"})
        store.dispatch({type:"USER_UP_NAME"})
    }
​
  render() {
    return (
      <div>
          <h1>redux的基本使用</h1>
          {/* 3.读取 */}
          <h1>使用数据---{this.state.name}</h1>
          <button onClick={this.fun}>点我修改上面的数据</button>
      </div>
    )
  }
}
​
合并reducer(把redux拆分成一个个的模块)

随着项目的体积越来越大 项目的state和修改的动作也会越来越多

1.新建一个reducer.js(就是一个合并工厂 把今后拆分的一个个的小模块合并起来)

2.新建一个文件夹modules 里面放置我们拆分的一个个的小模块

3.开始拆分 把原来写在一起的state和原来写在一起的动作拆分出来

// 里面存放的就是demoa的state数据和demoa的修改动作
let data={
    name:"xixi",
 
}
​
let demoam=(state=data,action)=>{
    // 创建修改动作
    switch (action.type) {
        case "USER_UP_NAME":
            console.log({...state,name:"我变了"})
            return {...state,name:"我变了"}
            break;
    
        default:
            return state
            break;
    }
}
​
export default demoam

4.开始合并reducers.js中进行

// reducer合并工厂中吧modules文件夹中多个小模块进行合并
// 1.把你要合并的所有模块引用进来
import demoam from "./modules/demoam.js"
import demobm from "./modules/demobm.js"
// 2.引用合并模块的方法
import {combineReducers} from "redux"
// 3.开始合并
let reducer=combineReducers({
    demoam,
    demobm
})
// 4.暴露
export default reducer

5.把合并好的模块 注入到redux实例中

import {createStore} from "redux"// 引用合并好的reducer
import reducer from "./reducers.js"let store=createStore(reducer)
​
export default store

大家会发现 我们合并好模块之后 在页面不显示数据了 因为我们把内容都合并成了模块所以要使用的时候

store.getState().模块名.xxxx

redux的数据执行流程

1

react-redux

react-redux 是一个专门为react开发的状态管理工具 而redux是第三方的

之前redux的写法 和react的耦合度太高 (在react中吧第三方的redux集成在项目里面 会造成代码的可读性太低)

react-redux 就可以简化我们在react中使用redux的复杂度

使用

1.下载 npm install --save react-redux 如果没有下载redux也要下

2.我们需要在项目的全局组件之上 设置Provider 发布者

import React from 'react';
import ReactDOM from 'react-dom';
​
import App from "./components/demob.jsx"// 1.引用provider
import { Provider } from "react-redux"
import store from "./store/index.js"
ReactDOM.render(
  // 2.使用provider把redux对象传递到所有的子组件身上
  // 3.传入store对象
  <Provider store={store}>
    <App />
  </Provider>,
​
  document.getElementById('root')
);
​
​

2.设置组件与redux的连接

import React, { Component } from 'react'
// 1.引用react-redux给我们提供的连接方法
// connect是一个函数  当这个函数被调用的时候就是一个高阶组件
import {connect} from "react-redux"
class demob extends Component {
    add=()=>{
    
    }
    del=()=>{
    
    }
  render() {
    return (
      <div>
          demob
          <h1>读取redux存储的age</h1>
          <button onClick={this.add}>点我+1</button>
          <button onClick={this.del}>点我-1</button>
    </div>
    )
  }
}
// connect是一个函数  当这个函数被调用的时候就是一个高阶组件
// 第一个() 就是调用函数的语法
// 第二个() 就是高阶组件的语法
export default connect()(demob)

3.得到redux中的数据

import React, { Component } from 'react'
// 1.引用react-redux给我们提供的连接方法
// connect是一个函数  当这个函数被调用的时候就是一个高阶组件
import {connect} from "react-redux"
class demob extends Component {
    add=()=>{
    
    }
    del=()=>{
    
    }
  render() {
    return (
      <div>
          demob
          {/* 使用react-redux读取数据 this.props.state.模块名.xxx */}
          <h1>读取redux存储的age--{this.props.state.demobm.age}</h1>
          <button onClick={this.add}>点我+1</button>
          <button onClick={this.del}>点我-1</button>
    </div>
    )
  }
}
// connect是一个函数  当这个函数被调用的时候就是一个高阶组件
// 第一个() 就是调用函数的语法
// 第二个() 就是高阶组件的语法
// 形参的state今后就是redux中的数据
export default connect(state=>({state}))(demob)
修改

在组件中调用dispatch就可以直接完成修改操作

   add=()=>{
        // react-redux修改数据
        // 还是使用dispatch触发action的动作
        this.props.dispatch({type:"AGE_NUMBER_ADD_ONE",num:3})
    }

17. 路由

根据url的不同来切换对应的组件页面

路由可以实现spa单页面应用 一个项目只有一个完整的页面 我们通过切换页面的显示内容 已达到不刷新页面进行切换的效果

路由分类

react-router库

仅仅只包含了路由最基本的功能没有一些辅助的api 但是他轻量级

react-router-dom库

除了基本的路由功能以外 还有很多便捷性的api方便我们开发者实现路由功能

npm install --save react-router-dom@5

路由模式

HashRouter

HashRouter 哈希路由: 使用url的hash值

BrowerRouter

BrowserRouter 浏览器历史记录路由: 使用H5的history API

BrowserRouter和HashRouter的不同点

1.兼容性不同

  • BrowserRouter 因为使用了H5的history API,不兼容IE9及以下
  • HashRouter 因为使用了url的哈希值兼容性更好

2.表现形式不同

  • HashRouter 地址栏带 #, BrowserRouter则不带 #

3.刷新对路由state参数的影响

  • BrowserRouter无影响,路由state保存在history对象中
  • HashRouter有影响,刷新后导致路由state参数丢失

实现

1.下载 npm install --save react-router-dom@5

2.设置路由模式 index.js中设置

import React from 'react';
import ReactDOM from 'react-dom';
​
// 1.引用路由模式
import {HashRouter} from "react-router-dom"ReactDOM.render(
  // 2.设置路由模式  包裹根组件
  <HashRouter>
    <xxxx></xxxx>
  </HashRouter>,
​
document.getElementById("root"))

3.开始设置路由页面 写在views或者pages

4.设置路由规则与出口 新建router文件夹在新建index.js

import React, { Component } from 'react'
// 1.把你要用的路由页面进行引用
import Home from "../views/home.jsx"
import Phone from "../views/phone.jsx"
import User from "../views/user.jsx"
import Shop from "../views/shop.jsx"
// 2-1 引用Route
import {Route} from "react-router-dom"export default class index extends Component {
  render() {
    return (
      <>
        {/* 路由规则与路由出口 */}
        {/* <Route path="/你的路径" component={你要引用的组件}/> */}
        {/* 2-2.配置出口与规则 */}
        <Route path="/home" component={Home}/>
        <Route path="/shop" component={Shop}/>
        <Route path="/user" component={User}/>
        <Route path="/phone" component={Phone}/>
    
​
      </>
    )
  }
}
​

5 设置路由配置组件为根组件index.js中

import React from 'react';
import ReactDOM from 'react-dom';
// 引用路由配置组件
import Index from "./router/index.js"// 1.引用路由模式
import {HashRouter} from "react-router-dom"ReactDOM.render(
  // 2.设置路由模式  包裹根组件
  <HashRouter>
    {/* 注入路由组件 */}
    <Index></Index>
  </HashRouter>,
​
document.getElementById("root"))

路由导航

声明式

<关键字 to="去哪里"></关键字>

Link 就是一个组基本的路由导航

NavLink 处理基本路由跳转的功能以外 还添加了自动选中类名的设置 active类名

但是 如果这个active的类名 已经存在了怎么办?

修改navlink的选中类名 activeClassName="你要修改的类名"

注意

有的同学navlink可能在电脑上不加类名 如果出现这种问题 那么就不要用vscode内置的cmd打开项目 而是用外部的cmd启动项目即可解决

编程式

push方法在路由页面中跳转 this.props.history.push("/xxxx")

常见问题

如果编程式导航中跳转的话 那么会出现 push of undefined的错误

原因:

是因为编程式导航只能在被路由所管理的页面中进行使用(被管理的页面是指 在路由规则中配置过的页面)因为不是被路由所管理的页面就不具备路由所提供的 三个属性(location match history)

所以就不能使用this.props.history 所以就会报错

解决方式:

使用withRouter 高阶组件(HOC)来解决 因为通过withRouter 可以让不是被路由所管理的页面也具有路由跳转的三个属性

使用:

1.引用withRouter

import {withRouter} from "react-router-dom"

2.修改组件的export default 到最下面

3.使用withRouter来设置当前组件

export default withRouter(当前组件)

更多跳转方式

replace() 替换当前路径

goBack()后退

goForward()前进

二级多级路由

1.编写二级路由页面

2.配置规则与出口 只需要在对应的一级路由页面中进行route的配置

import React, { Component } from 'react'import {Route,NavLink} from "react-router-dom"import Era from "./er/era.jsx"
import Erc from "./er/erc.jsx"
export default class phone extends Component {
  render() {
    return (
      <div>
          phone
          <NavLink to="/phone/era">era</NavLink>
          <NavLink to="/phone/erc">erc</NavLink>
          
          {/* 配置二级路由规则与出口 */}
          <Route path="/phone/era" component={Era}/>
          <Route path="/phone/erc" component={Erc}/>
        </div>
    )
  }
}
​

404页面

1.创建页面

2.配置404页面规则

  <>
       {/* 路由导航的组件 */}
        <Bb></Bb>
        {/* 路由规则与路由出口 */}
        {/* <Route path="/你的路径" component={你要引用的组件}/> */}
        {/* 2-2.配置出口与规则 */}
        <Route path="/home" component={Home}/>
        <Route path="/shop" component={Shop}/>
        <Route path="/user" component={User}/>
        <Route path="/phone" component={Phone}/>
​
​
        {/* 404页面必须放在最下面 */}
        <Route component={No}/>
​
      </>

大家会发现当我们加入了404页面之后 每个页面都会把404显示出来 因为reacrt的路由在匹配到之后 还会一直向下进行 直到最后

Switch 唯一渲染

用switch包裹的内容会渲染上之后不继续向下进行 只会匹配到第一个匹配的内容

 {/* 唯一渲染 */}
        <Switch>
            {/* 2-2.配置出口与规则 */}
            <Route path="/home" component={Home}/>
            <Route path="/shop" component={Shop}/>
            <Route path="/user" component={User}/>
            <Route path="/phone" component={Phone}/>
​
​
            {/* 404页面必须放在最下面 */}
            <Route component={No}/>
        </Switch>

重定向与精准匹配

重新定位方向

exact 精准匹配 只能是指定的内容才能匹配

{/* 唯一渲染 */}
        <Switch>
            {/* 2-2.配置出口与规则 */}
            <Route path="/home" component={Home}/>
            <Route path="/shop" component={Shop}/>
            <Route path="/user" component={User}/>
            <Route path="/phone" component={Phone}/>
​
            {/* 重定向 */}
            {/* 精准匹配   exact */}
            <Redirect from="/" to="/home" exact/> 
            {/* 404页面必须放在最下面 */}
            <Route component={No}/>
        </Switch>

路由传参

params方式

1.配置路径

 <Route path="/home/:xxx" component={Home}>

2.发送

声明式

 <NavLink to="/phone/数据"></NavLink>

编程式

 this.props.history.push("/phone/数据")

3.接收

this.props.match.params.xxx

优势 : 刷新地址栏,参数依然存在

缺点 : 只能传字符串,并且,如果传的值太多的话,url会变得长而丑陋。

query方式

1.发送

<Link to={{ pathname : '/d' , query : { name : 'sunny' }}}>点我去d</Link>
​
​
this.props.history.push({ pathname : '/d' , query : { name : 'sunny' }})

2.接受

this.props.location.query.xxx

优势:传参优雅,传递参数可传对象;

缺点:刷新地址栏,参数丢失

state方式

1.发送

<Link to={{ pathname : '/d' , state: { name : 'sunny' }}}>点我去d</Link>

2.接受

this.props.location.state.name

优势:传参优雅地址栏不显示传递的数据,传递参数可传对象;

缺点:刷新地址栏,参数丢失

路由拦截

render调用一个函数那么我们就可以决定什么时候渲染他 同时传入props那么就可以在路由组件中使用history: {…}, location: {…}, match: {…}这几个对象

    <Route path="/home" render={(props)=>{return <Home {...props}/>}}>

16 HOC--高阶组件

在React组件的构建过程中,常常有这样的场景,有一类功能需要被不同的组件公用。(React 中用于重用组件逻辑的高级技巧

HOC--参数是组件同时返回值也是组件

HOC不仅仅是一个方法,确切说应该是一个组件工厂,获取低阶组件(功能较少),生成高阶组件(添加多个组件复用的功能)

应用场景

需要代码重用时, react如果有多个组件都用到了同一段逻辑, 这时,就可以把共同的逻辑部分提取出来,利用高阶组件的形式将这段逻辑整合到每一个组件中, 从而减少代码的逻辑重复

例子:

比如在多个函数中我们都想使用一个相同的方法。这个方法就是自动发出一段异步请求 并且把请求成功的数据赋值给一个state在页面展示。

那么传统方式我们就需要把这个请求在每个组件中都是用一次 组件少了可以。但是组件变多就很麻烦 那么我们就需要想办法解决这个重复的代码 所以我们可以使用HOC来进行组件中公共内容的复用

大家发现下面的例子 这个请求可能会在多个组件都要使用 那么我们可以把他设置成一个HOC

import axios from 'axios'
import React, { Component } from 'react'
​
export default class two extends Component {
    state={
        text:""
    }
//    发送请求
   async componentDidMount (){
      let {data}= await axios({url:"/api/data/cityinfo/101320101.html"})
    //   把数据赋值给state 并且在下方展示
      this.setState({
          text:data.weatherinfo.city
      })
    }
    
  render() {
    return (
      <div>
        {this.state.text}
      </div>
    )
  }
}
​

HOC创建

1.新建withxxx文件 用来容纳HOC(高阶组件明明规则 是withxxx 比如withRouter)

// HOC本质就是一个函数
export default ()=>{
​
}
​

2.使用HOC 在需要服用的组件中把刚才封装的HOC引用使用

import axios from 'axios'
import React, { Component } from 'react'
// 1.引用高阶组件
import withLink from "./withLink.js"
// 2.修改暴漏在最下面
class two extends Component {
    state={
        text:""
    }
​
   async componentDidMount (){
      let {data}= await axios({url:"/api/data/cityinfo/101320101.html"})
​
      this.setState({
          text:data.weatherinfo.city
      })
    }
    
  render() {
    return (
      <div>
        {this.state.text}
      </div>
    )
  }
}
// 3.把当前组件当成函数的实参传入进去(因为高阶组件参数是一个组件)
export default withLink(two)

3.在高阶组件中设置行参接受传递进来的组件并且返回一个组件

// HOC本质就是一个函数import React from "react"// 设置行参接受(首字母要大写)
export default (Component)=>{
    // 并且返回值函数一个组件(组件可以是函数组件也可以是类组件)
    return class withLinkComponent extends React.Component{
​
        render(){
            return (
                <>
            
                </>
            )
        }
    }
}
​

4.移植逻辑

// HOC本质就是一个函数import React from "react"
import axios from "axios"// 设置行参接受(首字母要大写)
export default (Component) => {
    // 并且返回值函数一个组件(组件可以是函数组件也可以是类组件)
    return class withLinkComponent extends React.Component {
​
        // 把之前的逻辑放置进来
        state = {
            text: ""
        }
​
        async componentDidMount() {
            let { data } = await axios({ url: "/api/data/cityinfo/101320101.html" })
​
            this.setState({
                text: data.weatherinfo.city
            })
        }
        // 把之前的逻辑放置进来
​
        render() {
            return (
                <>
                    {/* 把数据传递出去 */}
                    <Component {...this.state}></Component>
                </>
            )
        }
    }
}
​

组件中使用

import axios from 'axios'
import React, { Component } from 'react'
// 1.引用高阶组件
import withLink from "./withLink.js"
// 2.修改暴漏在最下面
class two extends Component {
​
    
  render() {
    return (
      <div>
          {/* 组件内使用高阶组件的数据 */}
        {this.props.text}
      </div>
    )
  }
}
// 3.把当前组件当成函数的实参传入进去(因为高阶组件参数是一个组件)
export default withLink(two)

高阶组件---反向继承

HOC的反向继承 说的简单点就是渲染劫持 在使用HOC的时候可以进行一个高阶组件中的条件渲染

15 react ajax

在前后端分离项目中,前端请求后端接口得到后端数据,完成页面内容的渲染或功能状态的判断,已经成为常规操作。那么,关于前端如何请求后端接口获取并解析数据,主要有哪些方式呢:

1.jquery ajax方式

jquery比较重 不建议在项目中使用

2.fetch

fetch 最新的前后台异步数据交互的方式 传统的方式 axios或者是原生 jqueryajax 都是基于XMLHttpRequest方法来进行实现的 但是fetch不是因为Es最新规范中给我们提供了fetchAPI通过这个fetch

Api来进行前后台的数据交互

import React, { Component } from 'react'export default class demob extends Component {
    componentWillMount(){
        console.log("will")
    }
    componentDidMount() { 
        console.log("did")
        // fetch使用
        fetch("http://localhost:8888/one")
        // 把数据转换成json
        .then(ok=>ok.json())
        .then((ok)=>{
            console.log(ok)
        })
​
     }
  render() {
    return (
      <div>demob</div>
    )
  }
}
​

其他的方式

import React, { Component } from 'react'export default class demob extends Component {
    componentWillMount(){
        console.log("will")
    }
    componentDidMount() { 
        console.log("did")
        // fetch使用
        fetch("http://localhost:8888/one")
        // 把数据转换成json
        .then(ok=>ok.json())
        .then((ok)=>{
            console.log(ok)
        })
​
        // 发送数据get
        fetch("url/?key=val&key=val",{method:"GET"})
        // 把数据转换成json
        .then(ok=>ok.json())
        .then((ok)=>{
            console.log(ok)
        })
​
        // 发送post
        fetch(
            "url",{
                method:"POST",
                body:"key=val&key=val"
            }
        )
        // 把数据转换成json
        .then(ok=>ok.json())
        .then((ok)=>{
            console.log(ok)
        })
​
     }
  render() {
    return (
      <div>demob</div>
    )
  }
}
​

3.axios

axios 是目前最优秀的 HTTP 请求库之一,虽然 axios 已经封装的非常好了,我们可以直接拿过来用。但是在实际的项目中,我们可能还需要对 axios 在封装一下,以便我们更好的管理项目和各个接口。

axios常见api

1

axios.request

该方法是axios项目的核心处理方法,实现用户自定义配置、应用拦截器、发送请求核心功能

axios其他api

就是发送相关请求 比如get请求 delete请求等

get方式--params发送参数
    //   axios.get("请求地址",{params:{发送数据key:发送的val}}).then((ok)=>{
    //     // 成功回调
    //   }).catch((err)=>{
    //     // 失败回调
    //   })
      axios.get("/api/userlist/get",{params:{name:"xixi"}}).then((ok)=>{
        console.log(ok)
      }).catch((err)=>{
        console.log(err)
      })
    }
post方式--data发送参数
        //   axios.post("请求地址",{data:{发送的key:发送的val}}).then((ok)=>{
    //     // 成功回调
    //   }).catch((err)=>{
    //     // 失败回调
    //   })
      axios.post("/api/userlist/post",{data:{name:"xixi"}}).then((ok)=>{
        console.log(ok)
      }).catch((err)=>{
        console.log(err)
      })

但是大家会发现后台接收不到我们发送的数据

原因是因为:

在发送post的时候Content-type(表示请求和响应中的媒体类型信息。它用来告诉服务端如何处理请求的数据,以及告诉客户端(一般是浏览器)如何解析响应的数据)常见有三种形式:

  • Content-Type: application/json 就是给后台的数据是一个json对象
  • Content-Type: application/x-www-form-urlencoded 表单数据编码为键值对,&分隔 如:name=java&age = 23
  • Content-Type: multipart/form-data 通常文件上传

现在最主流的是application/json形式axios默认就是这种方式 就像上面我们写的post代码 直接把后该要的参数放到data中就可以了

1

如图可以发现我们的请求方式

但是有时候后端要求Content-Type必须以application/x-www-form-urlencoded形式,那么通过上面application/json传递的参数,后端是收不到的,我们必须对参数数据进行所谓的序列化处理才行,让它以普通表单形式(键值对)发送到后端,而不是json形式

用qs模块来序列化参数

我们也能通过第三方依赖来序列化参数,就更加方便简洁,下载qs模块。

1.下载 npm install --save qs

2.引用 import qs from “qs”

3.在传递数据的时候使用qs序列化

let key=qs.stringify({
            key:val
})
​
import React, { Component } from 'react'
import axios from "axios"
// 引用qs
import qs from 'qs';
export default class demo extends Component {
    componentDidMount = () => {
    // 序列化数据
    let key=qs.stringify({
        name:"xixi"
    })
        // 传递
      axios.post("/api/userlist/post",key).then((ok)=>{
        console.log(ok)
      }).catch((err)=>{
        console.log(err)
      })
    }
    
  render() {
    return (
      <div>
        
      </div>
    )
  }
}
​

1

delete put 等方式

delete方式 同get

 axios.delete("/api/userlist/delete",{params:{name:"xixi"}}).then((ok)=>{
        console.log(ok)
      }).catch((err)=>{
        console.log(err)
      })

put方式 同post

import React, { Component } from 'react'
import axios from "axios"
// 引用qs
import qs from 'qs';
export default class demo extends Component {
    componentDidMount = () => {
    // 序列化数据
    let key=qs.stringify({
        name:"xixi"
    })
        // 传递
      axios.put("/api/userlist/put",key).then((ok)=>{
        console.log(ok)
      }).catch((err)=>{
        console.log(err)
      })
    }
    
  render() {
    return (
      <div>
        
      </div>
    )
  }
}
​
​

跨域

方式1

同vue一样也是对devServer进行代理跨域 但是唯一的不同的写跨域的文件不一样

需要到node_modules/react-scripts/config/webpackDevServer.config.js中进行配置

找到proxy 进行设置

proxy:{
        "/api(可以随便写)":{
             target:"请求地址",
             changeOrigin:true,
             "pathRewrite":{
               "^/api(和上面一样)":"/"
             }
        }
    },

但是不要忘了修改请求的地址为/api 与重启项目

方式2:http-proxy-middleware

1.下载:npm install http-proxy-middleware --save

2.在项目的src路径下创建setupProxy.js

3.写入如下内容

const { createProxyMiddleware } = require('http-proxy-middleware');
​
module.exports = function(app) {
  app.use(
    '/api',
    createProxyMiddleware({
      target: 'http://www.weather.com.cn/',
      changeOrigin: true,
      pathRewrite:{
        "^/api":"/"
      }
    })
  );
};

4.修改组件的请求为/api

axios怎么封装

在大型项目中 http请求会有很多 而且我们需要区分 所以我们可以把数据请求拆分出来 单独管理这样一来 就可以增强项目的可维护性与可扩展性

大体分为如下内容

  1. api 请求集中式管理
  2. 实现请求拦截
  3. 实现响应拦截
  4. 常见错误信息处理
  5. 请求头设置

api请求集中管理

1.初始化axios实例 编写util文件夹 在编写index.js来容纳封装内容

虽然axios中已经给我们封装好了一些常见的api如上面的axios.get axios.post等

但是我们为了更好的全局控制所有请求的相关配置,所以我们使用 axios.create()创建实例的方法来进行相关配置,这也是封装 axios 的精髓所在。

通过 create() 方法我们得到了一个 axios 的实例,该实例上有很多方法,比如拦截器等等。我们创建实例的时候可以配置一些基础设置,比如基础请求地址,请求超时等等。

  import axios from "axios"
  // 创建 axios 请求实例
  const serviceAxios = axios.create({
      baseURL: "", // 基础请求地址
      timeout: 10000, // 请求超时设置
  });
  export default serviceAxios

2.新建api文件夹 并且编写js文件用来容纳 api请求集中管理

import request from "../util/index.js"export let funget=()=>{
    return request({
        url:"/api/userlist/get",
        method:"GET",
        params:{
            name:"xixi"
        }
    })
}

3.在组件中进行使用我们封装的 api请求集中管理

import React, { Component } from 'react'
// 引用
import {funget} from "../api/index.js"export default class demo extends Component {
    componentDidMount = () => {
        // 使用
        funget().then((ok)=>{
            console.log(ok)
        })
    }
  render() {
    return (
      <div>
        
      </div>
    )
  }
}
​
​

我们就简单的划分出 API 管理层了,这样我们每次新增加一个 API,只需要找到对应模块的 API 文件去添加即可,然后再到具体页面导入使用就行啦。

添加拦截器

添加请求 响应拦截

import axios from "axios"
// 创建 axios 请求实例
const serviceAxios = axios.create({
    baseURL: "", // 基础请求地址
    timeout: 10000, // 请求超时设置
​
});
// 添加请求拦截器
serviceAxios.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    return config;
}, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
});
​
// 添加响应拦截器
serviceAxios.interceptors.response.use(function (response) {
    // 对响应数据做点什么
    return response;
}, function (error) {
    // 对响应错误做点什么
    return Promise.reject(error);
});
​
​
​
export default serviceAxios
响应拦截设置HTTP状态码

得到错误http状态码

// 添加响应拦截器
serviceAxios.interceptors.response.use(function (response) {
    // 对响应数据做点什么
    return response;
}, function (error) {
    // 对响应错误做点什么
​
    // 得到错误http状态码
    console.log("响应",error.response.status)
    return Promise.reject(error);
});
// 添加响应拦截器
service.interceptors.response.use(function (response) {
    // 对响应数据做点什么
    return response;
}, function (error) {
    console.log("error", error.response.status)
    switch (error.response.status) {
        case 302: alert('接口重定向了!'); break;
        case 400: alert('参数不正确!'); break;
        case 401: alert('您未登录,或者登录已经超时,请先登录!'); break;
        case 403: alert('您没有权限操作!'); break;
        case 404: alert('请求地址出错'); break; // 在正确域名下
        case 408: alert('请求超时!'); break;
        case 409: alert('系统已存在相同数据!'); break;
        case 500: alert('服务器内部错误!'); break;
        case 501: alert('服务未实现!'); break;
        case 502: alert('网关错误!'); break;
        case 503: alert('服务不可用!'); break;
        case 504: alert('服务暂时无法访问,请稍后再试!'); break;
        case 505: alert('HTTP版本不受支持!'); break;
        default: alert('异常问题,请联系管理员!'); break
    }
    // 对响应错误做点什么
    return Promise.reject(error);
});
设置请求拦截请求头信息

你把用户的token以请求头的方式给后台

地址:/userlist/routertoken

方式 get

参数: 头信息是usertoken

返回值??

fetch VS axios VS ajax区别

1.传统的ajax 就是值使用XMLHttpRequest方法实现的数据请求 他隶属于原生的js 核心就是XMLHttpRequest对象 如果多个请求有先后顺序的话 那么容易造成回调地狱问题

jqueryajax 就是对原生XMLHttpRequest封装

2.axios 是基于promise封装的 本质上还是XMLHttpRequest的封装 只不过他是基于最新的语法进行封装的

3.fetch 就是原生js最新标准 和XMLHttpRequest没有半点关系

弹射

能不弹就不要弹 在弹射之前确定好你在弹 因为弹射出现的公司任何问题本人不负责

弹射不可逆 (弹射就是把原来配置文件隐藏的层级很深 现在弹到根路径下)

npm run eject 可能会出现错误 让你使用git提交完再次弹射

扩展---umi

Umi,中文可发音为乌米,是可扩展的企业级前端应用框架。Umi 以路由为基础的(就是解决了之前使用react-router-dom的时候使用路由的复杂度)

安装

1.全局安装 : npm install -g umi@2 / yarn global add umi@2

2.测试安装版本: umi -v

3.新建文件夹 把cmd的路径切换到这个文件夹下

4.开始创建项目 yarn create @umijs/umi-app / npm create @umijs/umi-app

5.下载依赖:cd到你刚才创建项目的文件夹下 npm install / yarn

6.运行 yarn start npm start

配置式路由的创建

1.在src下的pages文件夹下 新建你对应的路由页面

2.配置路由规则 在.umirc.ts文件中进行配置

import { defineConfig } from 'umi';
​
export default defineConfig({
  nodeModulesTransform: {
    type: 'none',
  },
  routes: [
    { path: '/', component: '@/pages/index' },
    // 仿照默认的内容自行添加
    { path: '/home', component: '@/pages/home/home' },
    { path: '/phone', component: '@/pages/phone/phone' },
  ],
  fastRefresh: {},
});
​

路由导航

编程式--js的方式

history.push("去哪里")

import React from 'react'
// 注意引用是来自于umi的
import {Link,history} from "umi"
export default function Topbar() {
    let fun=()=>{
        // 编程式导航
        history.push("/phone")
    }
  return (
    <div>
        <Link to="/">首页</Link>
        <Link to="/home">home</Link>
        <Link to="/phone">手机</Link>
        <hr />
        <button onClick={fun}>点我去手机</button>
​
    </div>
  )
}
​

声明式--标签

Link 这个标签来完成 使用to属性来表明路径

import React from 'react'
// 注意引用是来自于umi的
import {Link} from "umi"
export default function Topbar() {
  return (
    <div>
        <Link to="/">首页</Link>
        <Link to="/home">home</Link>
        <Link to="/phone">手机</Link>
​
    </div>
  )
}
​

重定向 --redirect

umirc文件里面进行配置

import { defineConfig } from 'umi';
​
export default defineConfig({
  nodeModulesTransform: {
    type: 'none',
  },
  routes: [
    { path: '/index', component: '@/pages/index' },
    // 仿照默认的内容自行添加
    { path: '/home', component: '@/pages/home/home' },
    { path: '/phone', component: '@/pages/phone/phone' },
    // 路由重定向    exact精准匹配
    { path: '/', redirect:"/index",exact:true },
  ],
  fastRefresh: {},
});
​

404页面

1.创建对应404页面

2.配置404规则

import { defineConfig } from 'umi';
​
export default defineConfig({
  nodeModulesTransform: {
    type: 'none',
  },
  routes: [
    { path: '/index', component: '@/pages/index' },
    // 仿照默认的内容自行添加
    { path: '/home', component: '@/pages/home/home' },
    { path: '/phone', component: '@/pages/phone/phone' },
    // 路由重定向    exact精准匹配
    { path: '/', redirect:"/index",exact:true },
​
    // 404页面
    { path: '*', component: '@/pages/no/no' },
  ],
  fastRefresh: {},
});
​

多级路由

1.创建多级路由页面

2.配置多级路由规则(在对应的以及路由规则中进行嵌套)

 { 
      path: '/home', 
      component: '@/pages/home/home',
      // 配置多级路由
      routes:[
        { path: '/home/era', component: '@/pages/er/era' },
        { path: '/home/erc', component: '@/pages/er/erc' },
      ]
    
    },

3千万不要忘了 设置二级路由出口 (在对应的以及路由页面中配置)props.children 多级路由的出口

3千万不要忘了 设置二级路由出口 (在对应的以及路由页面中配置)props.children 多级路由的出口

3千万不要忘了 设置二级路由出口 (在对应的以及路由页面中配置)props.children 多级路由的出口

3千万不要忘了 设置二级路由出口 (在对应的以及路由页面中配置)props.children 多级路由的出口

3千万不要忘了 设置二级路由出口 (在对应的以及路由页面中配置)props.children 多级路由的出口

3千万不要忘了 设置二级路由出口 (在对应的以及路由页面中配置)props.children 多级路由的出口

3千万不要忘了 设置二级路由出口 (在对应的以及路由页面中配置)props.children 多级路由的出口

3千万不要忘了 设置二级路由出口 (在对应的以及路由页面中配置)props.children 多级路由的出口


约定式路由

umi在创建路由的时候 我们也可以不需要配置路由规则 从而达到 路由效果 那么这种方式就叫约定式

创建页面两种方式

1.手工创建 同原来

2.吊的方式 命令创建

在项目的根路径下 输入 umi g page 你要创建的页面名 (就会在pages文件夹下 自动创建对应的页面)

扩展---dva

dva 首先是一个基于 reduxredux-saga 的数据流方案,然后为了简化开发体验 (dva就是对react进行了语法上面的简化 让我们在react中使用状态管理工具变得更简单)

redux-saga 就是redux进行异步操作的一个技术

使用

1.全局安装 npm install -g dva-cli

2.测试 dva -v

3.cd到指定文件夹之下 dva new 你的项目名

4.cd到项目下面

5.启动 npm start

路由

在dva中使用他内置的路由我们可以把路由页面创建到src下的routes文件夹中 当然我们也可以重新创建一个文件夹来容纳路由页面

1.创建路由页面

2.配置路由规则 src文件夹下的router.js文件中来进行编写

import React from 'react';
import { Router, Route, Switch } from 'dva/router';
import IndexPage from './routes/IndexPage';
// 1.引用路由页面
import Home from "./routes/home.jsx"
import Phone from "./routes/phone.jsx"
import User from "./routes/user.jsx"function RouterConfig({ history }) {
  return (
    <Router history={history}>
      <Switch>
        <Route path="/" exact component={IndexPage} />
        {/* 2.配置路由规则 */}
        <Route path="/home" exact component={Home} />
        <Route path="/phone" exact component={Phone} />
        <Route path="/user" exact component={User} />
      </Switch>
    </Router>
  );
}
​
export default RouterConfig;
​

路由导航

编程式--js

props.history.push("/去哪里") 注意 使用编程式导航必须 实在被路由所管理的页面中进行才可以 否则就要使用withRouter这个高阶组件来传递路由跳转的属性

import React from 'react'// 引用Link组件来自于dva 的路由
import {Link,withRouter} from "dva/router"function topbar(props) {
    let fun=()=>{
        // 编程式导航\
        // 在组件中定义了一个编程式导航
        // 但是在运行的时候发现push没有定义
        // 因为 在使用编程式导航的时候   由于当前组件不是被路由所管理的页面  那么他就不具备路由跳转的功能
        // 就会出现push没有定义
​
        // 所以我们就要使用HOC--高阶组件--withRouter来解决
        props.history.push("/user")
    }
  return (
    <div>
        
        {/* 声明式导航 */}
        <Link to="/">首页</Link>
        <Link to="/home">home</Link>
        <Link to="/phone">phone</Link>
        <Link to="/user">user</Link>
​
        {/* 编程式 */}
        <button onClick={fun}>点我去user</button>
​
    </div>
  )
}
​
export default withRouter(topbar)

声明式--标签组件

Link

import React from 'react'// 引用Link组件来自于dva 的路由
import {Link} from "dva/router"export default function topbar() {
  return (
    <div>
        
        {/* 声明式导航 */}
        <Link to="/">首页</Link>
        <Link to="/home">home</Link>
        <Link to="/phone">phone</Link>
        <Link to="/user">user</Link>
​
    </div>
  )
}
​

model

dva 通过 model 的概念把一个领域的模型管理起来,包含同步更新 state 的 reducers,处理异步逻辑的 effects,订阅数据源的 subscriptions 。(dva中的model 就是用来把状态管理工具进行模块化 设置的 为什么要设置模块化 因为组件多了 数据与修改就变得很多 如果我们把一个项目的所有数据与修改都放在一起 后期维护就很麻烦 所以这个时候我们就可以 进行模块拆分)

1.在src的modules文件夹中 创建对应的模块文件

2.编写内容

export default {
​
    namespace: 'userm',//命名空间---就是给当前这个模块起个名字(唯一的)
  
    state: {
        name:"我是user模块中的数据"
    },//存放数据的
  
    subscriptions: {//类似于一个监听  在里面可以绑定很多个监听的方案
    
    },
  
    effects: {//类似于vuex的actions  进行异步触发的
    
    },
  
    reducers: {//  修改上面的state的地方
     
    },
  
  };
  

3.把我们新建的模块和项目建立关联

在srtc下的index.js中建立

import dva from 'dva';
import './index.css';
​
// 1. Initialize
const app = dva();
​
// 2. Plugins
// app.use({});// 3. Model  就是建立我们模块与项目的位置
// app.model(require('./models/example').default);
app.model(require('./models/homem').default);
app.model(require('./models/phonem').default);
app.model(require('./models/userm').default);
​
// 4. Router
app.router(require('./router').default);
​
// 5. Start
app.start('#root');
​

读取数据

import React from 'react'
import Topbar from "../components/topbar.jsx"
// 1.引用组件和模块的链接工具--connect  是一个方法  当这个方法被调用的时候他才是一个链接的高阶组件
import { connect } from 'dva'function Home(props) {
  return (
    <div>
        <Topbar></Topbar>
        H
        {/* props.state.你要引用的模块的命名空间名字.数据名 */}
        <h1>我想读取dva中模块的数据----{props.state.homem.name}</h1>
        <h1>我想读取dva中另一个模块的数据----{props.state.userm.name}</h1>
    </div>
  )
}
// 2.设置数据
export default connect(state=>({state}))(Home)

修改数据

1.在组件内通过dispatch来触发修改

import React from 'react'
import Topbar from "../components/topbar.jsx"
import { connect } from 'dva'
 function Phone(props) {
    let fun=()=>{
        // 触发dva的数据修改
        // dispatch({type:模块的命名空间名字/你的reducer的名字})
        props.dispatch({type:"phonem/UP_NAME"})
    }
  return (
    <div>
        <Topbar></Topbar>
        phone----{props.state.phonem.name}
        <button onClick={fun}>点我修改数据</button>
    </div>
  )
}
export default connect(state=>({state}))(Phone)

2.编写对应修改的reducers

export default {
​
    namespace: 'phonem',//命名空间---就是给当前这个模块起个名字(唯一的)
  
    state: {
        name:"我是phone模块中的数据"
    },//存放数据的
  
    subscriptions: {//类似于一个监听  在里面可以绑定很多个监听的方案
    
    },
  
    effects: {//类似于vuex的actions  进行异步触发的
    
    },
  
    reducers: {//  修改上面的state的地方
        // state:就是上面的数据源
        // payload就是接收dispatch的第二个参数
        UP_NAME(state,payload){
            return {...state,name:"我吧phone的数据修改了"}
        }
    },
  
  };
  

effects--异步触发

扩展-Generator

在dva中如果我们要使用effects来进行异步触发 那么我们需要使用es6中新特性之 Generator

Generator 函数 就是一个在es6中新出的一个特性 他的作用就是让我们的函数可以根据我们开发者的需求走走停停

观察:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        // Generator 函数 就是一个在es6中新出的一个特性   他的作用就是让**我们的函数可以根据我们开发者的需求走走停停**
​
        // 观察如下代码  大家会发现当这个函数被调用的时候   函数体里面的内容  会全部自动执行完毕
        function fun(){
            console.log(111);
            console.log(222);
            console.log(333);
        }
        fun()
    </script>
</body>
</html>

思考:

我们能不能限制函数中的代码执行(根据我们的需要让函数体里面的东西执行或者暂停)

那么就可以使用generator函数 :

1.如果我们想控制函数的执行或者暂停 那么我们需要把函数变成generator函数 就是在function关键字 与函数名之间加一个*号

    function *fun(){
            console.log(111);
            console.log(222);
            console.log(333);
        }

2.我们需要让函数根据我们的需要走走停停 那么我们就必须在函数中加入表示 来表明暂停的位置是哪里 yield(就是函数暂停的分割线)

   function *fun(){
            console.log(111);
            yield
            console.log(222);
            yield
            console.log(333);
        }

3.我们在调用的时候 还需要告诉当前的generator函数 你要执行 next()

    function *fun(){
            console.log(111);
            yield
            console.log(222);
            yield
            console.log(333);
        }
        // 我们要执行函数中的内容
        let funger=fun()
        // 想让函数执行
        funger.next()
        funger.next()

effect使用

1.在组件中还是使用dispatch来触发

import Topbar from "../components/topbar.jsx"
import {connect} from "dva"
 function User(props) {
    let fun=()=>{
        // 触发effects进行异步操作
        // props.dispatch({type:"你要调用的模块命名空间名字/effect的名字"})
        props.dispatch({type:"userm/LINK"})
    }
  return (
    <div>
        <Topbar></Topbar>
        user
        
       <button onClick={fun}>点我触发异步操作</button> 
    </div>
  )
}
export default connect()(User)

2.编写effects异步触发器

export default {
​
    namespace: 'userm',//命名空间---就是给当前这个模块起个名字(唯一的)
  
    state: {
        name:"我是user模块中的数据"
    },//存放数据的
  
    subscriptions: {//类似于一个监听  在里面可以绑定很多个监听的方案
    
    },
  
    effects: {//类似于vuex的actions  进行异步触发的  effects的函数必须是一个Generator函数
        *LINK(){
            console.log("我被触发了");
        }
    },
  
    reducers: {//  修改上面的state的地方
     
    },
  
  };
  

3.设置请求

在dva中默认已经帮助我们封装好了异步请求 我们不需要单独去配置与下载 在service文件夹下新建文件 创建请求

import request from '../utils/request';
​
export function query() {
  return request('http://localhost:8888/userlist/getlink');
}
​

4.把写好的请求在effects中使用

// 1.引用我们刚才写好的请求
import {query} from "../services/userlink.js"
export default {
​
    namespace: 'userm',
  
    state: {
        name:"我是user模块中的数据"
    },
  
    subscriptions: {
    
    },
  
    effects: {
        // 2.使用异步请求
        // call 调用异步操作 并且得到成功的值
        *LINK({payload},{put,call}){
            console.log("我被触发了");
            // 下一步 使用call来获取成功的返回值
          let data= yield call(query
        }
    },
  
    reducers: {
     
    },
  
  };
  

5.把请求成功的数据复制给state方便组件使用( 先要交给reducers )

// 1.引用我们刚才写好的请求
import {query} from "../services/userlink.js"
export default {
​
    namespace: 'userm',
  
    state: {
        name:"我是user模块中的数据"
    },
  
    subscriptions: {
    
    },
  
    effects: {
        // 2.使用异步请求
        // call 调用异步操作 并且得到成功的值
        // put把effects中触发reducers
        *LINK({payload},{put,call}){
            console.log("我被触发了");
            // 下一步 使用call来获取成功的返回值
          let data= yield call(query
​
        //   把请求来的数据通过reducers修改给state
        // 下一步  通过put触发reducers
            yield put({type:"UP_NAME",data})
​
        }
    },
  
    reducers: {
     
    },
  
  };
  

6编写对应的reduicers

// 1.引用我们刚才写好的请求
import {query} from "../services/userlink.js"
export default {
​
    namespace: 'userm',
  
    state: {
        name:"我是user模块中的数据"
    },
  
    subscriptions: {
    
    },
  
    effects: {
        // 2.使用异步请求
        // call 调用异步操作 并且得到成功的值
        // put把effects中触发reducers
        *LINK({payload},{put,call}){
            console.log("我被触发了");
            // 下一步 使用call来获取成功的返回值
          let data= yield call(query
​
        //   把请求来的数据通过reducers修改给state
        // 下一步  通过put触发reducers
            yield put({type:"UP_NAME",data})
​
        }
    },
  
    reducers: {
        // 设置修改
        UP_NAME(state,payload){
            return {...state,name:payload.data}
        }
    
    },
  
  };
  

7 在页面读取数据

import Topbar from "../components/topbar.jsx"
import {connect} from "dva"
 function User(props) {
    let fun=()=>{
        // 触发effects进行异步操作
        // props.dispatch({type:"你要调用的模块命名空间名字/effect的名字"})
        props.dispatch({type:"userm/LINK"})    
    }
  return (
    <div>
        <Topbar></Topbar>
        user---{props.state.userm.name}
        
       <button onClick={fun}>点我触发异步操作</button> 
    </div>
  )
}
export default connect(state=>({state}))(User)

8 在运行之后出现了跨域问题 添加跨域 项目的根路径下有一个webpackrc的文件中添加跨域

{
    "proxy":{
        "/api":{
             "target":"http://localhost:8888",
             "changeOrigin":true,
             "pathRewrite":{
               "^/api":"/"
             }
        }
    }
}

9修改请求地址为/api

import request from '../utils/request';
​
export function query() {
  return request('/api/userlist/getlink');
}
​

10修改下请求的传递数据

// 1.引用我们刚才写好的请求
import {query} from "../services/userlink.js"
export default {
​
    namespace: 'userm',
  
    state: {
        name:"我是user模块中的数据"
    },
  
    subscriptions: {
    
    },
  
    effects: {
        // 2.使用异步请求
        // call 调用异步操作 并且得到成功的值
        // put把effects中触发reducers
        *LINK({payload},{put,call}){
            console.log("我被触发了");
            // 下一步 使用call来获取成功的返回值
          let data= yield call(query)
          console.log(data.data.msg);
​
        //   把请求来的数据通过reducers修改给state
        // 下一步  通过put触发reducers
            yield put({type:"UP_NAME",data:data.data.msg})
​
        }
    },
  
    reducers: {
        // 设置修改
        UP_NAME(state,payload){
            return {...state,name:payload.data}
        }
    },
  
  };
  

subscriptions 设置一个监听

可以在里面设置一些监听函数 必须页面修改了 鼠标移动了等等等

16 性能优化

完成如下效果

xingneng1

代码如下

xingneng2

xingneng3

在子组件的render中添加一个输出 在运行的时候发现每次运行的时候子组件都被调用了多次

那么大家发现当前我们写的有严重的性能问题;

原因是因为 我们在更新数据的时候使用setState修改整个数据 那么数据变了 便利的时候所有内容都要被重新渲染。

数量少没有关系 如果便利出来的数据很多 那么就会严重影响我们的性能

解决方式

使用生命周期的: shouldComponentUpdate(nextProps最新的props,nextState最新的state) 判定组件是否要更新

xingneng5

解决方式2 纯组件(PureComponent)--类组件中使用

PureComponent是优化react应用程序最重要的方法,组件发生更新时,组件的props和state没有改变,render方法就不会触发 .可以减少不必要的render次数,提高性能。

xingneng6

17 HOOK

react HOOK 是react16.8新增的一个特性 主要作用 就是让无状态组件/函数组件 可以使用状态 ref等一些特性

HOOK 不能在 class组件中使用

类组件与函数组件的区别

无状态组件 的创建比类组件的可读性更好 就是大大减少了组件编写代码量 在组件内容只有一个jsx编写效率更高

函数组件中不用this 因为函数组件没有实例化的过程

useState

useState 是reactHOOK给我们提供的 最基本最常用的一个HOOK 主要作用就是用来管理当前本地的状态

useState() 返回值是一个数组(长度为2)数组的第一项标识的是当前的值 数组的第二项标识的时候修改这个值的函数

let [xiaoming , setXiaoming]=useState(初始值)

创建与读取

import {useState} from "react"
let Funcom=()=>{
    // 使用useState()c创建函数组件的状态
    let [xiaoming,setxiaoming]=useState("你好么么哒!!!")
    return (
        <div>
            {/* 读取 */}
            <h1>我是一个函数组件--{xiaoming}</h1>
        </div>
    )
}
export default Funcom

修改

import {useState} from "react"
let Funcom=()=>{
    // 使用useState()创建函数组件的状态
    let [xiaoming,setxiaoming]=useState("你好么么哒!!!")
    return (
        <div>
            {/* 读取 */}
            <h1>我是一个函数组件--{xiaoming}</h1>
            {/* 修改数据 */}
            <button onClick={()=>{setxiaoming(xiaoming="你好呵呵哒")}}>点我修改</button>
        </div>
    )
}
export default Funcom

创建多个状态呢

1.你写多个useState

import {useState} from "react"
let Funcom=()=>{
    // 1.你写多个useState了解
    let [xiaoming,setxiaoming]=useState("1")
    let [xiaohong,setxiaohong]=useState("2")
  
    return (
        <div>
          {xiaoming}---{xiaohong}
        </div>
    )
}
export default Funcom

2.一次行创建多个值

import {useState} from "react"
let Funcom=()=>{
    // 1.你写多个useState了解
    // let [xiaoming,setxiaoming]=useState("1")
    // let [xiaohong,setxiaohong]=useState("2")
  
    // 2.一次性创建多个值
        let [xiaoming,setxiaoming]=useState({
            dataa:"第一个值1",
            datab:"第一个值2",
            datac:"第一个值3",
            datad:"第一个值4",
            datae:"第一个值5",
            dataf:"第一个值6"
        })
    return (
        <div>
          {/* {xiaoming}---{xiaohong} */}
​
          {xiaoming.dataa}----{xiaoming.datad}
        </div>
    )
}
export default Funcom

一次性创建多个值怎么修改

1.多个useState 要修改的话就依次调用其中的修改方法

2.一次行创建多个值的方式如何修改呢

import {useState} from "react"
let Funcom=()=>{
    // 1.你写多个useState了解
    // let [xiaoming,setxiaoming]=useState("1")
    // let [xiaohong,setxiaohong]=useState("2")
  
    // 2.一次性创建多个值
        let [xiaoming,setxiaoming]=useState({
            dataa:"第一个值1",
            datab:"第一个值2",
            datac:"第一个值3",
            datad:"第一个值4",
            datae:"第一个值5",
            dataf:"第一个值6"
        })
​
       let fun=()=>{
        //    一次性创建多个的修改操作   不要忘了保留原始数据
        setxiaoming({...xiaoming,datad:"我被改了"})
       }
​
    return (
        <div>
          {/* {xiaoming}---{xiaohong} */}
​
          {xiaoming.dataa}----{xiaoming.datad}
          <button onClick={fun}>点我修改</button>
        </div>
    )
}
export default Funcom

useRef

就是可以让函数组件使用ref的一个技术

import {useRef} from "react"
let Funcom=()=>{
//    1.创建出useRef
    let xiaoming=useRef(null)
​
    let fun=()=>{
        // 3.获取
        console.log(xiaoming.current.value);
    }
    return (
        <div>
            {/* 2.绑定使用 */}
          <input type="text" ref={xiaoming}/>
          <button onClick={fun}>点我得到输入框的值</button>
        </div>
    )
}
export default Funcom

useEffect

Function Component 不存在生命周期,所以不要把 Class Component 的生命周期概念搬过来试图对号入座。

useEffect就可以让函数组件使用生命周期

语法:

useEffect(第一个参数是一个函数,第二个参数是一个数组)

就是如下三个钩子函数的综合体

// 渲染完毕componentDidMount() {
​
  
​
}
​
// 修改完毕componentDidUpdate() {
​
  
​
}
​
// 准备销毁componentWillUnmount() {
​
  
​
}

模拟componentDidMount

useEffect(()=>{console.log('第一次渲染时调用')},[])

第二个参数为一个空数组,可以模拟componentDidMount

因为函数组件每次更新的时候就是组件会被全部调用一次 传递了空数组就相当于欺骗了react我要监听某个内容 所以只是第一次调用

import {useEffect} from "react"
let Funcom=()=>{
    useEffect(()=>{
        document.title="么么哒"
        console.log("我自动执行了");
    },[])
    return (
        <div>
           <h1>函数组件可以使用生命周期</h1>
        </div>
    )
}
export default Funcom

模拟componentDidUpdate

没有第二个参数代表监听所有的属性更新但是注意首次也会触发

因为函数组件每次更新的时候就是组件会被全部调用一次 什么都不传那么每次修改就会触发

useEffect(()=>{console.log('任意属性该改变')}) 
import React, { useState, useEffect } from 'react'export default function Demo() {
    let [xiaoming, setXiaoming] = useState("666")
    useEffect(() => {
        console.log("谁修改我都会触发")
    })
    let fun = () => {
        setXiaoming("我变了")
    }
    return (
        <>
            <div>demo--{xiaoming}</div>
            <button onClick={fun}>点我修改</button>
        </>
    )
}
​

监听多个属性的变化需要将属性作为数组传入第二个参数。

因为函数组件每次更新的时候就是组件会被全部调用一次 传递了值就会告诉react我要监听某个内容 所以只有这些值改变的时候就会被调用

useEffect(()=>{console.log('n变了')},[n,m]) 
import React, { useState, useEffect } from 'react'export default function Demo() {
    let [xiaoming, setXiaoming] = useState("666")
    let [xiaohong, setXiaohong] = useState("777")
    // 因为监听了xiaohong 所以修改小明的时候不会触发
    useEffect(() => {
        console.log("谁修改我都会触发")
    },[xiaohong])
    let fun = () => {
        setXiaoming("我变了")
    }
    return (
        <>
            <div>demo--{xiaoming}--{xiaohong}</div>
            <button onClick={fun}>点我修改</button>
        </>
    )
}
​

模拟componentWillUnmount

通常,组件卸载时需要清除 effect 创建的诸如订阅或计时器 ID 等资源

useEffect函数返回的函数可以表示组件死亡

import React,{useState,useEffect} from 'react'function Zi() {
    useEffect(()=>{
        // 调用子组件的时候执行一个定时函数
       let time= setInterval(()=>{
            console.log("你好")
        },1000)
        // 在effect中return 一个函数就是在销毁的时候执行
        return ()=>{
            console.log("我被销毁了")
            clearInterval(time)
        }
    })
    return (
      <div>我是Zi组件</div>
    )
  }
​
export default function Demo() {
    let [bool,setbool]=useState(true)
  return (
    <div>
        我是父组件
        <button onClick={()=>{setbool(!bool)}}>点我显示和隐藏子组件</button>
        {bool&& <Zi></Zi>}
       
        
    </div>
  )
}
​

useContext

接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。

可以直接获取到context对象的Consumer消费者中的数据

在没有使用的时候跨组件传值

import React, { createContext } from 'react'
let context = createContext()
function Sun() {
    return (
        <div>
            孙
            <context.Consumer>
                {
                    (value)=>{
                        return (
                            <h1>{value.name}</h1>
                        )
                    }
                }
            </context.Consumer>
        </div>
    )
}
​
function Zi() {
    return (
        <div>
            子
            <Sun></Sun>
        </div>
    )
}
​
export default function Fu() {
    return (
        <context.Provider value={{name:"xixi",age:18}}>
            <div>
                父
                <Zi></Zi>
            </div>
        </context.Provider>
    )
}
​

使用useContext

import React, { createContext,useContext } from 'react'
let context = createContext()
function Sun() {
    // 使用useContext可以直接得到Consumer中的数据
    let value=useContext(context)
    return (
        <div>
            孙
            <h1>{value.name}</h1>
        </div>
    )
}
​
function Zi() {
    return (
        <div>
            子
            <Sun></Sun>
        </div>
    )
}
​
export default function Fu() {
    return (
        <context.Provider value={{name:"xixi",age:18}}>
            <div>
                父
                <Zi></Zi>
            </div>
        </context.Provider>
    )
}
​

useCallback

思考下面的代码

我们直到当修改数据的时候这个函数是会被重新调用的 那么为什么每次++的时候会从上一次的结果加1

而不是从原始数据1开始计算呢?

所以可以理解为useState是一个记忆函数可以记住上次的状态

import { useState } from "react"
let Funcom = () => {
    let [xiaoming, setxiaoming] = useState(1)
    return (
        <div>
            <h1>{xiaoming}</h1>
            <button onClick={() => { setxiaoming(++xiaoming) }}>点我修改</button>
        </div>
    )
}
export default Funcom

useReducer

就是在react中 函数组件如果对一个state有多次修改 那么可以使用useReducer来对其内容进行简化

语法:

let [保存这个值得变量,是对这个变量修改的一个方法]=useReducer(修改数据的reducer方法,"初始化值")

import {useReducer} from "react"
let Funcom=()=>{
    // 因为useReducer是对一个数据需要有多次修改的时候使用的
    // state就是当前的这个状态
    // dispat触发修改操作
   
    let [state,dispatch]=useReducer(reducer,18)
    function reducer(state,action){
        switch (action.type) {
            case "ADD":
                return state+1
                break;
            case "DEL":
                return state-1
                break;
        
            default:
                return state
                break;
        }
    }
    var add=()=>{
        dispatch({type:"ADD"})
    }
    var del=()=>{
        dispatch({type:"DEL"})
    }
  
    return (
        <div>
​
            <h1>useReducer--{state}</h1>
            <button onClick={add}>+1</button>
            <button onClick={del}>-1</button>
        </div>
    )
}
​
export default Funcom
​
或者
import {useReducer} from "react"
let Funcom=()=>{
    // 因为useReducer是对一个数据需要有多次修改的时候使用的
    // state就是当前的这个状态
    // dispat触发修改操作
    var reducer=(state,action)=>{
        switch (action.type) {
            case "ADD":
                return state+1
                break;
            case "DEL":
                return state-1
                break;
        
            default:
                return state
                break;
        }
    }
    let [state,dispatch]=useReducer(reducer,18)
    
    var add=()=>{
        dispatch({type:"ADD"})
    }
    var del=()=>{
        dispatch({type:"DEL"})
    }
  
    return (
        <div>
​
            <h1>useReducer--{state}</h1>
            <button onClick={add}>+1</button>
            <button onClick={del}>-1</button>
        </div>
    )
}
​
export default Funcom

18 React与TS

创建项目 create-react-app 项目名 --template typescript

扩展 npx

创建项目:npx create-react-app 项目名 --template typescript

在 npm version >= 5.2.0 开始,自动安装了npx。

npx 这条命令会临时安装所依赖的环境。命令完成后原有临时下载的会删掉,不会出现在 全局中。下次再执行,还是会重新临时安装。不用全局安装,不用担心长期的污染。

也就是说 npx 会自动查找当前依赖包中的可执行文件,如果找不到,就会去 PATH 里找。如果依然找不到,就会帮你安装

组件

创建一个tsx后缀名的文件

4

在需要的位置引用使用 但是引用的时候可能会出错

5

.

6

在引用时候不要加后缀名

7

数据传递

使用传统的方式进行数据传递的时候发现会报错

8

9

在进行数据传递的时候要使用 interface接口对类型限制 在子组件中进行限制设定

10

状态

直接定义状态会出现问题

11

需要使用接口

12

逆向传值

13

14

React 的核心概念之一是组件。在这里,我们将引用 React v16.8 以后的标准组件,这意味着使用 Hook 而不是类的组件。

组件的创建

函数组件---无状态组件 可读性非常好 减少了大量的冗余代码 因为在函数组件中 只有一个jsx 简化了创建组件的便捷性

函数组件不会被实例化 渲染性能就大大提升了

函数组件中不用使用this

函数组件代码精简不用实例化 可以尽可能少的节省创建时候的内存消耗 提高性能

函数组件在默认情况下不能使用 状态 不能使用ref 不能使用生命周期

FC就是FunctionComponent的缩写ts+react编写组件的语法

// 后面的<>是范型 中间的{}是没有props state占位的
export let Home:React.FC<{}>=()=>{
    return (
        <div>我事一个函数组件</div>asd
    )
}
​

props的使用

sinterface IUser{
    title?:String
}
​
export let Home:React.FC<IUser>=(props)=>{
        return (
            <div>我事一个函数组件---{props.title}</div>
        )
    }

父组件正常传递