React 相关

1. react 创建项目

npm install -g create-react-app //安装脚手架
create-react-app myapp // 创建 react 项目
npm start // 启动项目

2. react 新建一个项目

  1. 可以删除 src 目录下的所有文件,
  2. 然后创建一个 index.js 文件

index.js文件中的内容


// react 可以识别并解析 jsx 语法
import React from "react";
// react-dom 可以渲染dom树,然后插入到页面中指定的元素中。
import ReactDOM from 'react-dom'

// react-dom 的render 方法可以渲染 dom 元素 ,等价于下面的 create 写法
ReactDOM.render(<div>111</div>,document.getElementById('root'))

// ReactDOM.render(React.createElement('div',{id:'aaa',class:'bbb'},'1111'),document.getElementById('root'))

3. react 类组件

  1. react 中创建类组件,需要从 react.component 中继承。记得要引入 react
  2. 创建完类组件以后需要,将创建的类组件 export default 暴露出去 (注意:vue中不需要暴露)
  3. 在需要的组件中 引入上面暴露的类组件
    注意:在引入class 类组件的时候,组件名字首字母要大写

react 组件化与vue组件化的区别。

  1. vue 组件不需要 export default 暴露,但是引入组件的时候,需要在 components 中注册引入的组件
  2. react 类组件需要 export default 暴露, 但是引入组件的时候,不需要注册 ,可以直接写一个 单标签或者双标签使用即可。
// 创建一个 react 类组件
import React from 'react'

export default class App extends React.Component {
  render() {
    return <div>hello,coco 现在测试 react类组件</div>
  }
}

//使用
import App from './01-base/01-class类组件'

ReactDOM.render(<App></App>, document.getElementById('root'))

4. react 函数式组件

  1. 可以直接写一个函数,在函数中 return jsx 语法
    注意:
  • 16.8 之前 ,函数时组件也叫 无状态组件
  • 16.8 之后,可以使用 react hooks 来改动 状态。

export default function App (){
  return (
    <div>
      hello coco,现在测试函数式组件!!!
    </div>
  )
}

// 也可以写成  声明变量和箭头函数的方式来创建 函数式组件
const Footer = () => <div>footer组件</div>

// 使用
import App from './01-base/02-fn函数式组件'

ReactDOM.render(<App></App>, document.getElementById('root'))

5. react 中编写css 样式的两种方式

  1. 行内样式
  2. 外部引入 css 样式,使用 className 来编写 css样式。
  3. 注意:写行内样式时,要在 jsx语法(花括号)中 进行编写。
import React, { Component } from 'react'
import './css/index.css'

export default class Style extends Component {
  render() {
    var styleObj = {
      background: 'tomato',
    }
    return (
      <div>
        <div style={{ background: 'red' }}>行内样式1</div>
        <div style={styleObj}>行内样式2</div>
        {/* 注意:react中 起class 类名需要使用 className ,使用 class 的话会被误认为 js中的关键字 */}
        <div className="componentStyle">行内样式2</div>
        {/* 同样的 for 在react jsx的语法中也属于是 关键字,所以下面这种情景需要 使用 htmlFor来规避这种问题 */}
        <label htmlFor="username">用户:</label>
        <input type="text" id="username"></input>
      </div>
    )
  }
}

6. react 事件绑定

  1. 函数声明式 , 在 jsx 语法中调用时不用带 小括号 ()
  2. 在匿名函数中调用 函数声明式,需要带小括号 ()

推荐

  1. 四种事件绑定中,方法二需要重新绑定 this的指向
  2. 其他三种方法因为有箭头函数,所以this指向与 外面(render)保持一致。所以不需要重新绑定this的指向。
  3. 推荐 使用第四种 事件绑定的方法,因为传递参数的时候比较方便
{/* 事件绑定1:箭头函数(匿名函数) */}
        <button
          onClick={() => {
            console.log('click1',this.a)
          }}
        >
          add1
        </button>
        {/* 事件绑定2:函数声明式*/}
        <button onClick={this.handleClick2.bind(this)}>add2</button>
        {/* 事件绑定3:函数声明式*/}
        <button onClick={this.handleClick3}>add3</button>
        {/* 事件绑定4:箭头函数(匿名函数) + 函数声明式*/}
        <button onClick={()=>{this.handleClick4()}}>add4</button>

        handleClick2() {
          console.log('click2',this.a)
        }
        handleClick3 = () => {
          console.log('click3',this.a)
        }
        handleClick4 () {
          console.log('click4',this.a)
        }

7. react ref 引用

方法一:直接使用 ref定义一个字符串,然后使用 this.refs.字符串 获取绑定的dom元素
方法二:使用 React.createRef ,创建一个变量,使用 this.变量.current 来获取dom元素

 mytext2 = React.createRef()
  render() {
    return (
      <div>
        {/* 方法一:使用ref */}
        <input ref="mytext"></input>
        <button
          onClick={() => {
            // 获取 dom 元素
            console.log(this.refs.mytext.value);
          }}
        >
          获取input的值
        </button>
        {/* 方法二:使用 React.createRef */}
        <input ref={this.mytext2}></input>
        <button
          onClick={() => {
            // 获取 dom 元素
            console.log(this.mytext2.current.value);
          }}
        >
          获取input的值
        </button>
      </div>
    )

8. react 条件渲染

1. 使用与,或运算符 进行条件渲染
 {this.state.active === 1 && <Buy></Buy>}

2. 使用三目运算符 进行条件渲染
 {this.state.active === 1 ? <Buy></Buy> : null}

9. react 组件之间属性的传递

类组件传参

  1. 在组件上定义属性名,形式 变量={ '内容' }
  2. 在子组件的 render 函数中通过 this.props.变量来获取从父组件传递属性.
父组件:
<Navbar title="首页" leftShow={false}></Navbar>

对象传参:
var obj = {
      title: '测试',
      leftShow: false,
    }
<Navbar {...obj}></Navbar>

Navbar组件:
import React, { Component } from 'react'

export default class index extends Component {
  render() {
    console.log(this.props,'父组件传递的属性')
    let { title, leftShow } = this.props
    return (
      <div>
        {leftShow && <button>返回</button>}
        navbar--{title}
        {leftShow && <button>首页</button>}
      </div>
    )
  }
}


函数式组件传参

  1. 传参方式和类组件的传参方式一样。
  2. 函数式组件获取参数的方式: 给函数式组件传入一个 props 参数,然后通过 props.属性来获取父组件传来的属性
import React from 'react'

1. 给函数式组件传入一个参数,用来接收父组件传递的属性。
export default function Sidebar(props) {

  2. 将父组件传来的属性进行结构赋值,也可以直接使用 props.属性来获取。
  let { content } = props
  return <div>{content}</div>
}

10. react dangerouslySetInnerHTML

  1. 相当于 vue的 v-html
  2. 用法:可以解析 html 标签

 <span dangerouslySetInnerHTML={{ __html: item.value }}></span>
 

11. react 请求数据

  1. 在没有学生命周期的时候,建议将请求放在 constructor 函数中。(注意:constructor函数中必须有 super()函数 )
  2. 请求数据最好在生命周期函数中进行。

12. react setState 更新状态注意事项:

  1. setState 处于同步逻辑中,异步更新状态和dom,
  2. setState 处于异步逻辑中,同步更新状态和dom,
  3. setState 第二个参数是一个回调函数, 在setState 更新完状态和dom后触发。
 state = {
    count: 1,
  }
  render() {
    return (
      <div>
        {this.state.count}
        <button onClick={this.handle1}>add1</button>
        <button onClick={this.handle2}>add2</button>
      </div>
    )
  }
  handle1 = () => {
    // 此时是同步逻辑,所以 setState 异步更新状态
    this.setState(
      {
        count: this.state.count + 1,
      },
      () => {
        console.log(this.state.count, 'setState第二个参数') //2
      }
    )
    console.log(this.state.count) //1

    this.setState(
      {
        count: this.state.count + 1,
      },
      () => {
        console.log(this.state.count, 'setState第二个参数') //2
      }
    )
    console.log(this.state.count) //1

    this.setState(
      {
        count: this.state.count + 1,
      },
      () => {
        console.log(this.state.count, 'setState第二个参数') //2
      }
    )
    console.log(this.state.count) //1
  }
  handle2 = () => {
    setTimeout(() => {
      // 此时 setState 处于异步逻辑,所以 setState 同步更新状态和 dom
      this.setState({
        count: this.state.count + 1,
      })
      console.log(this.state.count) //2

      this.setState({
        count: this.state.count + 1,
      })
      console.log(this.state.count) //3

      this.setState({
        count: this.state.count + 1,
      })
      console.log(this.state.count) //4
    }, 0)
  }

13. react 属性验证 prop-types

  1. 在需要使用属性验证的组件 引入 prop-types
  2. 在组件中定义一个类属性 static propTypes = { }
    • 注意:上面定义的类属性名是固定的.
  3. 在上面定义的类属性中 以 key:value的形式,进行属性验证

设置默认属性

  1. 在定义的class类中,通过 static defaultProps = { } 来设定从父组件传递属性的默认值
import React, { Component } from 'react'
// 1. 引入 prop-types 模块
import CocoProptypes from 'prop-types'

export default class index extends Component {
  // 2. 定义 propTypes 类属性,用来对父组件传递过来的属性进行验证.
  static propTypes = {
    title: CocoProptypes.string,
    leftShow: CocoProptypes.bool,
  }
	
  // 3. 设置默认属性
  static defaultProps = {
    leftShow: true,
  }
  // 可以查看 属性验证规则
  console.log(CocoProptypes)
  render() {
    let { title, leftShow } = this.props
    return (
      <div>
        {leftShow && <button>返回</button>}
        navbar--{title}
        {leftShow && <button>首页</button>}
      </div>
    )
  }
}

类属性 和 对象属性

  1. 类属性 通过定义的类 直接打点添加一个属性 ,也可以在 定义的class中使用 static 关键字来定义 类属性.
  2. 对象属性 在定义的class类中,不添加关键字定义的属性是对象属性

类属性 和 对象属性的读取方式

  1. 类属性,可以通过 定义的class类 直接打点调用(读取)
  2. 对象属性,首先要 new class() ,示例一个定义的class类,然后打点调用(读取)
class test {
  // 对象属性
  a = 100
  // 类属性
  static b = 88
}

// 类属性
test.a = 66

console.log(test.a, test.b, new test().a) // 66 88 100

14. react 父子组件通信

  1. 子组件要想改变父组件传过来的状态,可以触发父组件定义的事件来改变父组件的状态。
import React, { Component } from 'react'

class Navbar extends Component {
  render() {
    return (
      <div style={{ background: 'red' }}>
        <button
          onClick={() => {
            // 触发父组件定义的事件
            this.props.hideSidebar()
          }}
        >
          click
        </button>
      </div>
    )
  }
}

class Sidebar extends Component {
  render() {
    return (
      <div style={{ width: '30%', background: 'yellow' }}>
        <ul>
          <li>111111111</li>
          <li>111111111</li>
          <li>111111111</li>
          <li>111111111</li>
          <li>111111111</li>
          <li>111111111</li>
          <li>111111111</li>
          <li>111111111</li>
          <li>111111111</li>
          <li>111111111</li>
        </ul>
      </div>
    )
  }
}

export default class App extends Component {
  state = {
    isShow: false,
  }
  render() {
    return (
      <div>
        <Navbar
          // 自定义一个事件(回调函数),让子组件通过触发这个事件来改变父组件的状态。
          hideSidebar={() => {
            this.handle()
          }}
        ></Navbar>
        {this.state.isShow && <Sidebar></Sidebar>}
      </div>
    )
  }
  handle() {
    this.setState({
      isShow: !this.state.isShow,
    })
    console.log('navbar组件触发父组件')
  }
}


15. react 非父组件通信的几种方式

  1. 状态提升,把需要传递的属性,存放在一个公共的 父组件中,然后获取.
  2. 订阅与发布
  3. context 模式

订阅与发布

  1. 发布函数要在所有 订阅函数 最后执行.
  2. 接收参数 subscribe 使用订阅函数
  3. 传递参数 publish 使用发布函数
bus调度中心:

var bus = {
  list: [], //用于存放 订阅的函数.

  // 订阅函数 :每次调用 订阅函数的时候都往 list 中存入一个 回调函数
  subscribe(callback) {
    this.list.push(callback)
  },
  // 发布函数: 发布的时候,遍历 list 并逐个调用,调用回调函数的时候 可以给回调函数中传入 发布的参数.
  publish(val) {
    this.list.map((callback) => callback && callback(val))
  },
}

bus.subscribe((val) => {
  console.log('111', val)
})
bus.subscribe((val) => {
  console.log('222', val)
})
bus.subscribe((val) => {
  console.log('333', val)
})
bus.subscribe((val) => {
  console.log('444', val)
})

setTimeout(() => {
  bus.publish('我是发布者')
}, 0)

context 模式

  1. 首先全局创建一个 React.createContext()
  2. 然后根据 提供者(Provider) 消费者(Consumer) 的关系来,进行标签包裹
  3. 使用 Provider 包裹需要 组间通信组件的父组件.
  4. Provider 标签有一个value 属性, 可以给这个属性定义 一个函数,让子组件通过调用这个函数来改变父组件的状态.

context 标签的用法

1. provider 标签用法:
<GloablContext.Provider
        value={{
          name: 'coco',
          info: this.state.detail,
          changeInfo: (val) => {
            this.setState({
              detail: val,
            })
          },
        }}
      >
       
</GloablContext.Provider>

2. consumer 标签用法:
<GloablContext.Consumer>
        {(val) => {
          return (
           
          )
        }}
      </GloablContext.Consumer>
import React, { Component } from 'react'
import axios from 'axios'
import '../01-base/css/communite.css'

// 全局创建 context 函数
const GloablContext = React.createContext()

class FilmItem extends Component {
  render() {
    let { synopsis } = this.props
    return (
      <GloablContext.Consumer>
        {(val) => {
          return (
            <div
              className="filmitem"
              onClick={() => {
                // 点击之后可以通过调用 provider 提供的changeInfo 方法来,改变父组件的状态
                val.changeInfo(synopsis)
              }}
            >
              <img src={this.props.poster} alt={this.props.name}></img>
              <div>{this.props.name}</div>
            </div>
          )
        }}
      </GloablContext.Consumer>
    )
  }
}

class FilmDetail extends Component {
  render() {
    return (
      <GloablContext.Consumer>
        {(val) => {
          return <div className="filmdetail">{val.info}</div>
        }}
      </GloablContext.Consumer>
    )
  }
}

export default class App extends Component {
  constructor() {
    super()
    this.state = {
      filmList: [],
      detail: '',
    }
    axios.get('/test.json').then((res) => {
      console.log(res.data.data.films)
      this.setState({
        filmList: res.data.data.films,
      })
    })
  }
  render() {
    return (
      <GloablContext.Provider
        value={{
          name: 'coco',
          info: this.state.detail,
          changeInfo: (val) => {
            this.setState({
              detail: val,
            })
          },
        }}
      >
        <div>
          {this.state.filmList.map((item) => (
            <FilmItem {...item} key={item.filmId}></FilmItem>
          ))}
          <FilmDetail></FilmDetail>
        </div>
      </GloablContext.Provider>
    )
  }
}


16. react 插槽的使用

  1. 在子组件中 使用 this.props.children 占位来接收从父组件传递的插槽。
  2. this.props.children 是一个数组,可以通过 this.props.children[0] 来固定展示自己想要展示的插槽.
import React, { Component } from "react";

class Child extends Component {
  render() {
    return (
      <div>
        //11111 22222 33333
        {this.props.children}
        // 33333 22222 11111
        {this.props.children[2]}
        {this.props.children[1]}
        {this.props.children[0]}
      </div>
    );
  }
}

export default class App extends Component {
  render() {
    return (
      <div>
        <Child>
          <div>11111</div>
          <div>22222</div>
          <div>33333</div>
        </Child>
      </div>
    );
  }
}

17. react 生命周期

1. 初始化阶段

  • componentWillMount()
  • render()
  • componentDidMount()

注意:

  1. componentWillMount 声明周期 在 react 16.8 版本以后就不被推荐了.(控制台会有报错提醒)
    • 原因: 函数执行优先级不较低,会被高优先级的函数给打断.
    • 解决办法: 使用 UNSAFE_componentWillMount() 可以解决告警提示.

2. 运行中阶段

  1. componentWillReceiveProps(nextProps)
    • 该函数一般写在 子组件中.
    • 该函数 会最先获取到父组件传递过来的属性,在获取到父组件传递过来的属性之后,可以进行 ajax 数据请求
    • 也可以在该函数中将 父组件传递过来的属性转变为 自身组件的属性.
  2. shouldComponentUpdate(nextProps,nextState)
    • scu 性能优化函数
  3. componentWillUpdate() : 16.8 版本后也被弃用了, 使用 UNSAFE_ 解决报错提醒.
  4. render()
  5. componentDidUpdate()

注意:

  1. componentDidUpdate() 声明周期函数的缺点就是会多次调用,每当状态更新的时候,该函数就会被调用.
  2. componentDidUpdate(prevProps,prevState) 两个参数
    • prevProps 表示老的属性.
    • prevState 表示老的状态.
  3. shouldComponentUpdate(nextProps,nextState)
    • nextProps 表示新的属性
    • nextState 表示新的状态

3. 销毁阶段

  • componentWillUnmount()
  • 作用:在组件销毁时进行清除工作,比如计时器,事件监听器等.
    
import React, { Component } from "react";

class Child extends Component {
  componentDidMount() {
    window.onresize = () => {
      console.log("resize");
    };
    this.timer = setInterval(() => {
      console.log("111");
    }, 1000);
  }
  render() {
    return <div>child</div>;
  }
  componentWillUnmount() {
    window.onresize = null;
    clearInterval(this.timer);
    console.log("子组件销毁了");
  }
}

export default class App extends Component {
  state = {
    show: true,
  };
  render() {
    return (
      <div>
        <button
          onClick={() => {
            this.setState({ show: !this.state.show });
          }}
        >
          click
        </button>
        {this.state.show && <Child></Child>}
      </div>
    );
  }
}

18. react 新生命周期

getDerivedStateFromProps(nextProps,nextState)

  1. 该方法再根组件中可以代替 componentWillMount()生命周期函数。
  2. 在子组件中可以代替 componentWillReceiveProps()生命周期函数。
  • 该函数是 class 类中的静态方法,需要用static关键字去定义.
  • 该方法需要 return 一个空对象.
  • 该方法再初始化的时候会调用一次,数据更新的时候也会去调用.
  • 该方法可以配合 componentDidUpdate() 方法,优化 axios请求.
import React, { Component } from "react";

export default class App extends Component {
  state = {
    name: "coco",
  };

  static getDerivedStateFromProps(nextProps, nextState) {
    console.log("getDerivedStateProps");
    return {
      name: nextState.name,
    };
  }

  render() {
    return (
      <div>
        <button
          onClick={() => {
            this.setState({ name: "tiechui" });
          }}
        >
          click
        </button>
        {this.state.name}
      </div>
    );
  }
}

getSnapshotBeforeUpdate()

  1. 该生命周期函数不能与componentWillUpdate()同时出现,控制台会报错.
  2. 该生命周期函数可以用于获取 更新后 dom 元素的高度.
  3. 通过 componentDidUpdate(prevProps,prevState,value) ,这个生命周期函数的第三个值来获取 getSnapshotBeforeUpdate() 函数返回 dom 元素的高度.
import React, { Component } from "react";

export default class App extends Component {
  state = {
    name: "coco",
  };
  render() {
    console.log("render");
    return (
      <div>
        <button
          onClick={() => {
            this.setState({ name: "tiechui" });
          }}
        >
          click
        </button>
        {this.state.name}
      </div>
    );
  }
  componentDidUpdate(prevProps, prevState, value) {
    console.log("componentDidUpdate", value);
  }
  getSnapshotBeforeUpdate() {
    console.log("getSnapShotBeforeUpdate");
    return 100;
  }
}

案例

import React, { Component } from "react";
// 需求,每点击添加的时候,就往box添加数据,但是显示的仍然是当前 查看元素的位置.

export default class App extends Component {
  state = {
    list: [1, 2, 3, 4, 5, 6, 7, 8, 9],
  };
  box = React.createRef();
  render() {
    return (
      <div>
        <button
          onClick={() => {
            this.setState({
              list: [
                ...[11, 12, 13, 14, 15, 16, 17, 18, 19],
                ...this.state.list,
              ],
            });
          }}
        >
          click
        </button>
        <ul ref={this.box} style={{ height: "200px", overflow: "auto " }}>
          {this.state.list.map((item) => (
            <li key={item} style={{ height: "200px", background: "tomato" }}>
              {item}
            </li>
          ))}
        </ul>
      </div>
    );
  }
  getSnapshotBeforeUpdate(prevProps, prevState) {
    console.log(this.box.current.scrollHeight, "前");
    return this.box.current.scrollHeight;
  }
  componentDidUpdate(prevProps, prevState, value) {
    this.box.current.scrollTop += this.box.current.scrollHeight - value;
    console.log(this.box.current.scrollHeight, "后");
  }
}

19. react 性能优化

  1. shouldComponentUpdate()
  2. PureComponent()
import React, { PureComponent } from 'react'
export default class App extends PureComponent {
  render(){
    return ()
  }
}

20. react hooks

  1. hooks 是针对函数式组件的,可以让 函数式组件拥有自己的状态.

1. useState() 声明函数式组件自己的状态

  1. 在 react 中 引入 useState 这个函数
  2. useState('初始状态') ,声明变量的初始状态.
  3. const [text, setText] = useState(‘’)
    • text 即为函数式组件自己的状态 , useState(‘’) 中的参数就是这个状态初始化的值.
    • setText ,只可以通过这个 方法来改变 text 这个状态.
  4. useState() 也是记忆函数,可以记住当前的状态
import React, { useState } from "react";

export default function App() {
  const [text, setText] = useState("");

  const handleClick = () => {
    setText("coco");
  };
  return (
    <div>
      <div> text:{text}</div>
      <button onClick={handleClick}>click</button>
    </div>
  );
}

2. useEffect() 可以用于获取请求数据

  1. useEffect(()=>{},[]) 有两个参数.第一个是回调函数,第二个是依赖项.
  2. 如果在 useEffect 中对状态进行改变,则需要在 第二个依赖数组中 填写.否则,useEffect 中的状态改变时不会触发 useEffect 这个函数.(通俗讲就是监听useEffect函数中的状态,如果发生改变则重新触发 useEffect 这个函数.)
  3. useEffect() 回调函数中可以再 return ()=>{} 返回一个回调函数,这个回调函数是用于组件销毁时,清除计时器 或者 清除监听事件使用的.

使用注意:

  • useEffect 可以多次注册,不是说只可以注册使用一次.

useEffect() 和 useLayoutEffect() 的区别

  1. 首先两个函数在执行结果上是一致的.
  2. useLayoutEffect() 函数 就相当于 componentDidUpdate() 和 componentDidMount() 函数一样,都是在 react 执行完dom元素之后执行的(dom树).
  3. useEffect() 函数是在 页面渲染完之后执行的(渲染树).
    相关词汇: dom树渲染树

使用建议:

    1. 平时推荐使用 useEffect(),可以减少代码阻塞.
    1. 如果要对dom进行操作的时候,推荐使用 useLayoutEffect() 函数,可以减少回流和重绘.

3. useCallback(()=>{},[]) 用于缓存函数

  1. 在函数式组件中, 每当状态更新的时候,组件内定义的函数也会随着被重新定义.这样会造成不必要的性能损耗.
  2. useCallback() 的第二个参数(数组)中,填入 该函数所依赖的状态(依赖项),然后 useCallback 就会把这个定义的函数给缓存下来,每当依赖项发生变化的时候,函数才会被重新定义.
  3. 该函数也是性能优化的一种方式.该函数会返回一个函数.
const handle = useCallback(() => {
  first;
}, [second]);

4. useMemo() 类似与vue 中的计算属性.

  1. useMemo() 会返回一个函数的执行结果.
useMemo(() => () => {}, [second]);

5. useRef() 相当于React.createRef()

  1. 可以绑定 dom 元素,获取 dom 元素的相关属性.
  2. 可以保存 临时变量的值.
// 通过 useRef 保存临时变量
import React, { useState, useRef } from "react";

export default function App() {
  const [count, setcount] = useState(0);
  // 给 useRef 传入一个初始值0.
  var mycount = useRef(0);
  return (
    <div>
      <button
        onClick={() => {
          setcount(count + 1);
          mycount.current++;
        }}
      >
        add
      </button>
      {count}--{mycount.current}
    </div>
  );
}

6. useContext() 可以减少组件层级

  1. 提供(provider)参数的时候 还是要使用 标签来包裹需要组间通信的组件的.
  2. 子组件获取 参数的时候,通过 useContext() 来获取传递参数的对象.
  3. useContext(React.createContext()),参数要传入 react创建的 context函数.
// 全局创建 context 函数
const GloablContext = React.createContext()

// 提供参数的时候 还是要按照下面这种方式来写.
<GloablContext.Provider
        value={{
          name: 'coco',
          info: this.state.detail,
          changeInfo: (val) => {
            this.setState({
              detail: val,
            })
          },
        }}
      >

</GloablContext.Provider>

// 获取参数的时候 可以通过 useContext() 来获取

const value = useContext(GloablContext)

此时的value 值就是 provider value属性的值.
value = {
  name: 'coco',
  info: this.state.detail,
  changeInfo: (val) => {
    this.setState({
      detail: val,
    })
  },
}

7. useReducer(reducer,initailState) 用于复杂组件通信

  1. useReducer()useContext() 配合使用.
  2. 使用 useReducer() 函数,需要全局声明一个 initailState 对象来存放公共状态.
  3. 在 声明一个 reducer(prevState,actions) 函数,用来存放 不同的处理逻辑.
  4. 将声明的 reducer()函数initailState对象 当作参数传入 useReducer(reducer,initailState)
import React, { useReducer, useContext } from "react";

const reducer = (prevState, actions) => {
  console.log(prevState, "prevState");
  let newState = { ...prevState };
  switch (actions.type) {
    case "change-a":
      newState.a = actions.value;
      return newState;
    case "change-b":
      newState.b = actions.value;
      return newState;
    default:
      return prevState;
  }
};

const initailState = {
  a: "1111",
  b: "2222",
};

const GlobalContext = React.createContext();

export default function App() {
  const [state, dispatch] = useReducer(reducer, initailState);
  return (
    <GlobalContext.Provider
      value={{
        state,
        dispatch,
      }}
    >
      <div>
        <Child1></Child1>
        <Child2></Child2>
        <Child3></Child3>
      </div>
    </GlobalContext.Provider>
  );
}

function Child1() {
  const { dispatch } = useContext(GlobalContext);
  return (
    <div style={{ background: "tomato" }}>
      <button onClick={() => dispatch({ type: "change-a", value: "改变a" })}>
        change-a
      </button>
      <button onClick={() => dispatch({ type: "change-b", value: "改变b" })}>
        change-b
      </button>
    </div>
  );
}

function Child2() {
  const { state } = useContext(GlobalContext);

  return <div style={{ background: "yellow" }}>{state.a}</div>;
}
function Child3() {
  const { state } = useContext(GlobalContext);

  return <div style={{ background: "lightblue" }}>{state.b}</div>;
}

8. 自定义 hook 必须使用use定义

  1. 自定义 hook 可以用于抽离一些公共逻辑,用于复用.

21. react 路由

1. 安装

//安装 react 路由
npm i react-router-dom@5

2. 使用

  1. 从 'react-router-dom’组件中 引入 HashRouter Route 组件.
  2. 使用 HashRouter 标签包裹 Route 标签.
  3. 在 Route 标签上 标注path component 等属性进行路径和组件的映射.
import React, { Component } from "react";
import { HashRouter, Route, Redirect, Switch } from "react-router-dom";
import Films from "../views/films";
import Cinemas from "../views/cinemas";
import Center from "../views/center";
import NotFound from "../views/notFound";
export default class index extends Component {
  render() {
    return (
      <HashRouter>
        // switch 组件,当页面刷新的时候,使页面停留在当前路由组件.
        <Switch>
          <Route path="/films" component={Films}></Route>
          <Route path="/cinemas" component={Cinemas}></Route>
          <Route path="/center" component={Center}></Route>
          // exact属性 表示精准匹配.
          <Redirect from="/" to="/films" exact></Redirect>
          <Route component={NotFound}></Route>
        </Switch>
      </HashRouter>
    );
  }
}

3. react 路由重定向(Redirect)

  1. 可以在没有指定路由路径的时候,重定向到我们想要指定的组件.
  2. 一般配合 Switch 组件使用,因为单独使用 Redirect 组件,刷新页面的时候不会停留在当前组件,而会 重定向到 我们指定的路由.
  3. Redirect 组件标签有一个 exact 属性,表示精准匹配,再需要给组件添加 404 组件的时候,可以使用到 exact 这个属性.
  4. 如果只是写一个 from='/' 表示 模糊匹配,每当没有相匹配的路径时,都会重定向到我们指定的路由组件.

4. react 嵌套路由

  1. 在需要二级路由的地方,重新再写一套 路由跳转的方式.
//在 films 组件中嵌套二级路由.
import React, { Component } from "react";
import { Redirect, Route, Switch } from "react-router-dom";
// 引入films 的二级路由组件.
import Comingsoon from "./films/comingsoon";
import Nowplaying from "./films/comingsoon";

export default class films extends Component {
  render() {
    return (
      <div>
        films // 此时
        当films组件渲染的时候,就可以根据路径的映射去渲染二级路由的组件.
        <Switch>
          <Route path="/films/nowplaying" component={Nowplaying}></Route>
          <Route path="/films/comingsoon" component={Comingsoon}></Route>
          <Redirect from="/films" to="/films/nowplaying"></Redirect>
        </Switch>
      </div>
    );
  }
}

5. react 声明式导航

  1. react-router-dom 库中 引入 NavLink 组件.
  2. 将 NavLink 组件标签写在 HashRouter 组件标签内.(可以在封装的 路由组件中预留一个 插槽,用来放 NavLink)
  3. NavLink 组件标签有一个 activeClassName 属性,可以高亮当前显示的路由.
import { HashRouter, Route, Redirect, Switch, NavLink } from "react-router-dom";

<HashRouter>
  <NavLink activeClassName="cocoActive" to="/films">
    影院
  </NavLink>
  <NavLink activeClassName="cocoActive" to="/center">
    中心
  </NavLink>
  <NavLink activeClassName="cocoActive" to="/cinemas">
    电影
  </NavLink>
  <Switch>
    <Route path="/films" component={Films}></Route>
    <Route path="/cinemas" component={Cinemas}></Route>
    <Route path="/center" component={Center}></Route>

    <Redirect from="/" to="/films" exact></Redirect>
    <Route component={NotFound}></Route>
  </Switch>
</HashRouter>;

6. react 编程式导航

  1. 函数式组件,通过 props.history.push(‘/path’)
  2. 类组件,通过 this.props.history.push(‘/path’)
  3. hooks, 引入 useHistory() 函数, const history = useHistory() ,history.push(‘/path’)

7. react 路由传参

  1. 动态路由传参 ,需要在 path 路径上留变量占位符 ,刷新页面 参数不会丢.
  2. query 传参 ,不需要在 path 路径上留变量占位符 ,刷新页面 参数会丢失.
  3. state 传参,不需要在 path 路径上留变量占位符 , 刷新页面 参数会丢失.
1. 动态路由传参 (刷新页面,参数不会丢失)
// 路由标签 path
<Route path="/details/:id" component={Details}></Route>
// 路由跳转,携带参数
props.history.push(`/details/${id}`)
// 获取动态路由传递的参数
props.match.params.id

2. query路由传参 (刷新页面,参数会丢失)
<Route path="/details" component={Details}></Route>
// 路由跳转 携带参数.
props.history.push({pathname:'/details',query:{myid:id}})
// 获取 query 路由传递的参数
props.location.query.myid

3. state 路由传参 (刷新页面,参数会丢失)
<Route path="/details" component={Details}></Route>
// 路由跳转 携带参数.
props.history.push({pathname:'/details',state:{myid:id}})
// 获取 state 路由传递的参数
props.location.state.myid

8. react 路由拦截(路由守卫)

  1. react 使用路由拦截的时候,需要使用 Route 组件标签的 render={(props)=>{}}属性辅助.
  2. 在 render 函数的回调函数中,使用 if 条件语句 或 三目运算符 进行跳转判断.
  3. 如果满足条件 进行路由跳转,如果不满足条件 使用 Redirect 组件标签进行 路由重定向.

注意:使用 render()函数进行 路由拦截的时候, 回调函数内部返回的组件没有 props,需要我们将 render((props)=><Center {...props}>),render 函数 回调函数的 props 当作 属性传递给 组件.

// 注册 login 路由组件.
<Route path='/login' component={Login}></Route>

// 路由拦击 条件判断.
function isAuth() {
    return localStorage.getItem('token')
}

// 路由拦截,注册路由
<Route
  path="/center"
  render={(props) => {
    return isAuth() ? <Center {...props} /> : <Redirect to={"/login"}></Redirect>;
  }}
></Route>

// login 组件 内容
<div>
    <h1>登录页面</h1>
    <input type='text'></input>
    <button onClick={() => {
        localStorage.setItem('token', '123')
        this.props.history.push('/center')
    }}>登录</button>
</div>

9. 路由模式

  1. HashRouter :带 # 号,但是不会发生 把路径作为参数向后端请求数据的情况.
  2. BrowserRouter : 没有 # 号(好看),该种路由模式,会把 路径当作参数向后端发请求要页面,如果后端没有对应路径的处理路径,就会报 404 ,不好看.
  • 解决办法: 当因为后端没有对应路径处理路径而报 404 的时候,就让后端 刷新我们提供的 index.html 文件,即可.

10. withRouter

  1. 当 Route 包裹的路由组件 还有一个子组件的时候, 该路由组件的子组件是不能直接拿到 路由的相关 api 的.
  • 解决办法:将父组件 Films 组件的 props 同样当作属性传给 子组件.
  1. 从 react-router-dom 库中 ,引入 withRouter 函数.
<Route to='/films' component={Films}>

// films 组件 ,我们可以将父组件 Films组件的 props 同样当作属性传给 子组件.

list.map(item=>{
  <FilmsItem {...item} {...props}>
})

function FilmsItem (props){
  <li onClick={()=>{
    props.history.push('/detail')
  }}></li>
}

// 第二种解决方法
import {withRouter} from 'react-router-dom'

list.map(item=>{
  <withFilmItem {...item}>
})

function FilmsItem (props){
  <li onClick={()=>{
    props.history.push('/detail')
  }}></li>
}

const withFilmItem = withRouter(FilmsItem)

向外暴露组件时,使用 withRouter 包裹.

import React, { Component } from "react";
import { withRouter } from "react-router-dom";

class center extends Component {
  render() {
    return <div>center</div>;
  }
}

export default withRouter(center);

22. react 表单域组件(ref)

  1. react中可以通过 ref 来获取组件上的所有属性和方法。
1. 定义 ref
        username = React.createRef()
2. 绑定 ref
        <Field label="用户名" type="text" ref={this.username}></Field>
3. 点击获取 ref 组件上的所有属性
        console.log(this.username.current)

23. react 中使用map 报错 return a value in arrow function

问题 : 使用 arr.map(item=> {} ) 时会报上面的错误。
原因: 在eslint 语法规范下 react 将map 中的 {} 花括号认为是 jsx 语法,所以出现了上面的错误。
解决办法: 将花括号改为小括号 arr.map(item=> () )

24. react 中引入本地图片问题

参考文章 :

解决办法:

    1. import方法
    1. require方法
方法一:
import imgURL from './../images/photo.png';
...
...
<img src={imgURL } />

方法二:
<img src={require('./../images/photo.png')} />

25. react 反向代理

反向代理 和 正向代理

  1. 反向代理:隐藏服务端.
  2. 正向代理:隐藏客户端.

react 配置反向代理

npm install http-proxy-middleware --save

  1. 在 react 项目中的 src 目录下创建一个 setupProxy.js 文件
  2. 然后再 setupProxy.js 文件中写入相关配置.
  3. 重启服务器,每次修改完 setupProxy 文件之后都需要重启 服务,npm start.
const { createProxyMiddleware } = require("http-proxy-middleware");

module.exports = function (app) {
  app.use(
    "/api",
    createProxyMiddleware({
      target: "http://localhost:5000",
      changeOrigin: true,
      pathRewrite: {
        "^/api": "",
      },
    })
  );

  // 可以配置多个跨域
  app.use(
    "/api2",
    createProxyMiddleware({
      target: "http://localhost:5000",
      changeOrigin: true,
      pathRewrite: {
        "^/api": "",
      },
    })
  );
};

26. react 解决 css选择器冲突问题.

  1. name.moudle.css 来命名 css 文件.
  2. 在 js 文件中引入 import style from 'name.moudle.css'
  3. 获取 name.moudle.css 文件中的选择器, style.选择器名称

name.moudle.css 文件的缺点

缺点:使用 标签名选择器的时候 ,会影响到全局样式.
解决办法:可以在每个大组件的最外面 起不同的类名,然后每在 使用标签名选择器的时候,在前面加上这个最外面的类名.

name.moudle.css文件:
.active{
  color:red;
}

js文件获取:
import style from 'name.moudle.css'

<div className={style.active}></div>

27. react Redux

  1. npm i redux
  2. 创建一个 store.js 文件.
  3. 从 redux 中 引入 createStore( reducer )
npm i redux

store.js 文件:
import {createStore} from 'redux'

const reducer = (prevState,action)=>{
  return prevState
}
const store = createStore( reducer )

export default store

使用 redux

相关方法:

  1. store.dispatch()
  2. store.subscribe()
  3. store.getState()
// 传递参数的文件
import store from '../store.js'

useEffect(()=>{
  store.dispatch({
    type:'giveState'
  })
  return {
    store.dispatch({
      type:'detoryGiveState'
    })
  }
})

// 接收参数的文件

import store from '../store'
// 每次 redux ,通过 dispatch 触发action的时候,都会触发 store.subscribe() 方法.
// 在回调函数中 可以通过 store.getState() 方法来获取 redux更新后的最新数据.
store.subscribe(()=>{
  console.log(store.getState())
})

redux 简单实现原理

function createCocoStore(reducer) {
  let list = []
  let state = reducer(undefined, {})
  function dispatch(action) {
    state = reducer(state, action)
    for (let i in list) {
      list[i] && list[i]()
    }
  }

  function subscribe(callback) {
    list.push(callback)
  }

  function getState() {
    return state
  }

  return {
    dispatch,
    subscribe,
    getState,
  }
}

纯函数

  1. 对函数外界没有副作用.
  2. 同样的输入得到同样的输出.

28. react Redux-reducer合并

  1. 创建不同的 js 文件,用来管理不同的 reducer.
  2. 在 store.js 文件中 导入这些管理 reducer 的文件.
  3. 从 redux 中导入 combineReducers 函数.(用于不同 reducer 的合并)
import { combineReducers } from "redux";
import functionA from "./functionA.js";
import functionB from "./functionB.js";
import functionC from "./functionC.js";

const reducer = combineReducers({
  a: functionA,
  b: functionB,
  c: functionC,
});

// 获取状态:
store.getState().a.状态名;

29. react actionCreater

  1. 创建不同的 js 文件, 然后在声明的函数中返回一个对象。
  2. 最后暴露这个声明的函数。

30. react redux-thunk

redux-thunk 用于解决请求异步数据的问题

npm i redux-thunk

  1. 在 store.js 文件中,从 redux 中引入 ‘applyMiddleware()’;
  2. 引入 ‘redux-thunk’ .
  3. 在 store 上挂载.
import { createStore, applyMiddleware } from 'redux'
import reduxThunk from 'redux-thunk'

const store = createStore(applyMiddleware(reduxThunk))

用法: function aa() {
  return (dispatch) => {
    axios.get('/test').then(() => {
      dispatch({
        type: 'test',
        list: res.data.data,
      })
    })
  }
}

31. react redux-promise

npm i redux-promise

  1. 跟 redux-thunk 功能类似,都是用于异步获取数据.
  2. redux-thunk 返回一个回调函数; redux-promise 返回一个 promise.
import { createStore, applyMiddleware } from 'redux'
import reduxThunk from 'redux-thunk'
import reduxPromise from 'redux-promise'

const store = createStore(applyMiddleware(reduxThunk, reduxPromise))

用法: function aa() {
  return axios.get('/test').then(() => {
    return {
      type: 'test',
      list: res.data.data,
    }
  })
}

32. react 取消 redux 订阅

  1. 声明一个变量,接收订阅返回的函数.
  2. 在组件销毁的时候,调用上面接收订阅返回的函数.
useEffect(()=>{

  var destorySubscribe = store.subscribe(()=>{})

  return {
    // 取消订阅
    destorySubscribe()
  }
})

33. react react-redux

npm i react-redux

相关方法和属性

  1. provider 标签,包裹 跟组件.有一个 store 属性,需将引入的 store 当作属性值传递给该属性.
  2. connect() 函数,在暴露组件时用于包裹当前组件,该函数有两个参数
    • 第一个参数: 是一个回调函数,函数中需要返回一个对象,用于给将来的子组件传递属性.
    • 第二个参数: 是一个对象,用于给将来的子组件传递 函数(方法)
  1. 从 ‘react-redux’ 中引入 Provider , 并使用 <Provider> 标签包裹 App 组件.
  2. 在 App 组件中, 引入 connect 函数 (从react-dux中引入),在暴露组件的时候使用 connect()(App) 函数进行包裹.
  3. 该方法可以不用每次都手动的去 订阅取消订阅
import store from './redux/store.js'
import Provider from 'react-redux'
;<Provider store={store}>
  <App />
</Provider>

App组件: import { connect } from 'react-redux'

function Test() {
  return <div></div>
}

function a() {}
function b() {}

// connect 有两个参数,第一是回调函数 用于给将来的子组件传递属性,第二个回调函数是一个对象,用于给将来的子组件传递方法(函数.)

export default connect(
  () => {
    return {
      a: 属性值a,
      b: 属性值b,
    }
  },
  {
    fnA: a,
    fnB: b,
  }
)(Test)

react-redux 原理

function cocoConnect(cb, obj) {
  var value = cb()
  return (Mycomponent) => {
    return (props) => {
      return <Mycomponent {...value} {...props} {...obj}></Mycomponent>
    }
  }
}

34. react redux持久化

import { createStore } from 'redux'
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage' // defaults to localStorage for web
 
import rootReducer from './reducers'
 
const persistConfig = {
  key: 'root',
  storage,
}
 
const persistedReducer = persistReducer(persistConfig, rootReducer)
 
export default () => {
  let store = createStore(persistedReducer)
  let persistor = persistStore(store)
  return { store, persistor }
}


// index.js 文件中引入 persistGate 网关
import { PersistGate } from 'redux-persist/integration/react'
 
// ... normal setup, create store and persistor, import components etc.
 
const App = () => {
  return (
    <Provider store={store}>
      <PersistGate loading={null} persistor={persistor}>
        <RootComponent />
      </PersistGate>
    </Provider>
  );
};

35. react 支持装饰器语法

安装相关插件

npm i @babel/core @babel/plugin-proposal-decorators @babel/preset-env

创建.babelrc 文件

{ "presets": [ "@babel/preset-env" ],"plugins": [ [ "@babel/plugin-proposal-decorators", { "legacy": true } ] ] }

创建 config-overrides.js

const path = require('path') const { override, addDecoratorsLegacy } = require('customize-cra') function resolve(dir) { return path.join(__dirname, dir) }const customize = () => (config, env) => { config.resolve.alias['@'] = resolve('src') if (env === 'production') { config.externals = { 'react': 'React', 'react-dom': 'ReactDOM' } }return config };module.exports = override(addDecoratorsLegacy(), customize())

安装依赖

npm i customize-cra react-app-rewired

修改 package.json


"scripts": { "start": "react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test", "eject": "react-app-rewired eject" },

在vscode 中打开下面的配置,使vscode对 装饰器语法不报错
在这里插入图片描述

36. react styled-components

  1. npm i styled-component
  2. import styled from ‘styled-component’
import styled from "styled-component";

const styledFooter = styled.footer`
  background: yellow;
  border: 1px solid black;
  ul {
    li {
      color: red;
    }
  }
  &:hover {
    background: pink;
  }
`;

<styledFooter>
  <ul>
    <li></li>
  </ul>
</styledFooter>;

37. react redux-saga

相关方法

  1. take() : 监听触发 dispatch 的函数
  2. fork() : 执行异步处理函数
  3. call() : 发出异步请求,并获得异步请求的返回值.
  4. put() : 发起新的 dispatch()

saga 简单使用

1. app组件: 点击发起dispatch
 <button onClick={
    () => {
        if (store.getState().list.length === 0) {
            store.dispatch({
                type: 'get-list'
            })
        } else {
            console.log('读取缓存', store.getState().list);
        }
    }
}>
    请求ajax1
</button>


2. store文件:

import { createStore, applyMiddleware } from 'redux'
//添加 saga 中间件.
import createSagaMiddleware from 'redux-saga'
import watchSaga from './saga';
import reducer from './reducers'

// 创建 saga对象,并使用 applyMiddleware() 中间件进行包裹
const SagaMiddleware = createSagaMiddleware();
const store = createStore(reducer, applyMiddleware(SagaMiddleware))

// 监听 创建好的 saga任务.
SagaMiddleware.run(watchSaga)
export default store

3. saga.js文件:
import {takeEvery, take, fork, put, call , all} from "redux-saga/effects";

import watchSaga1 from './saga1'
import watchSaga2 from './saga2'

function* watchSaga() {

  // 监听多个 saga 文件,并执行
  yield all([watchSaga1(),watchSaga2()])

  // while (true) {
  //   // 该部分代码等同于 下面的 takeEvery()

  //   // take 监听触发dispatch的函数
  //   // fork 同步执行异步处理函数
  //   yield take("get-list");
  //   yield fork(getList);
  // }

  yield takeEvery('get-list',getList)

}

function* getList() {
  // 处理异步函数
  // call() 发出异步请求
  let res = yield call(getListAction);
  // put() 发出新的 action
  yield put({
    type: "change-list",
    payload: res,
  });
}

function getListAction() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(["111", "2222", "333"]);
    }, 2000);
  });
}

export default watchSaga;

38. react portal

  1. 可以将指定 dom 元素节点放在自己指定的地方.
  2. createPortal() 从 react-dom 引入该函数
    • 第一个参数:需要传送的 dom 元素.
    • 第二个参数:需要传送到的位置.
import { createPortal } from "react-dom";

function App() {
  return createPortal(<div></div>, document.body);
}

39. react 懒加载

  1. react 的懒加载(.lazy)属性需要配合 Suspense 标签使用。
import React, { Suspense } from 'react'
// 正常引入 js组件
// import Lazy1 from './lazy1'
// import Lazy2 from './lazy2'

// 懒加载引入 js组件
const Lazy1 = React.lazy(() => import('./lazy1'))
const Lazy2 = React.lazy(() => import('./lazy2'))

function Test() {
  return (
    <div>
      <Suspense fallback={<div>正在加载...</div>}>
        {this.type == 1 ? <Lazy1></Lazy1> : <Lazy2></Lazy2>}
      </Suspense>
    </div>
  )
}

40. react forwardRef

  1. forwardRef 函数可以透传,拿到子组件中的 dom 元素。
  2. forwardRef 需要从 react 中引入。
  3. forwardRef 函数需要传入一个 回调函数, 该回调函数有两个参数
    • 参数一:从父组件传递的 props 属性。
    • 参数二:从父组件传递的 ref 属性。
import React, { Component, forwardRef } from 'react'

export default class app extends Component {
  mytext = React.createRef()
  render() {
    return (
      <div>
        <button
          onClick={() => {
            this.mytext.current.focus()
            this.mytext.current.value = ''
            console.log(this.mytext.current)
          }}
        >
          获取焦点
        </button>
        <Child ref={this.mytext}></Child>
      </div>
    )
  }
}

const Child = forwardRef((props, ref) => {
  return (
    <div style={{ background: 'tomato' }}>
      <input ref={ref} defaultValue={'1111'}></input>
    </div>
  )
})

41. react memo 函数式组件优化

  1. 类似于 类组件中pureComponent() 函数钩子,但是 pureComponent 函数只能用在 类组件中.
  2. memo() 需要从 react 中引入 ,使用再函数式组件中.
    • 使用该函数创建的子组件,当父组件传递给子组件的属性发生变化的时候,子组件(函数式组件)才会重新渲染.
import React, { Component, memo } from 'react'

export default class app extends Component {
  state = {
    name: 'coco',
    text: '原始',
  }
  render() {
    return (
      <div>
        <button
          onClick={() => {
            this.setState({
              name: 'ike',
            })
          }}
        >
          修改
        </button>
        <button
          onClick={() => {
            this.setState({
              text: '修改',
            })
          }}
        >
          修改2
        </button>
        <div>{this.state.name}</div>
        <Child text={this.state.text}></Child>
      </div>
    )
  }
}

const Child = memo((props) => {
  console.log(1111)
  return <div style={{ background: 'tomato' }}>{props.text}</div>
})
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值