react基础使用

130 阅读18分钟

react学习笔记[js的库]

库是基于js的扩展并没有脱离js, 框架是脱离了原生的环境

安装脚手架:

npm i create-react-app -g

启动

create-react-app  项目名

特点: 组件化 跨平台 声明式开发[数据驱动]

react核心架构

1、入口文件: index.js

2、

react:核心库 
react-dom: 类似于vue中的app, 将组件的 虚拟dom节点渲染到index.html中
react-script: 隐藏配置文件

react支持插件

1、 在设置搜索emmet:在【Include Languages】 中添加jsx支持

2、 安装:ES7+ React/Redux/React-Native snippets v4.4.3 插件

JSX (xml in js)

注意:jsx中的标签都不是真是dom,可以看作标签时一个一个对象

jsx的基本语法:

1、jsx环境会将 {} 中的代码按照js来解析, 而且最终会编译成dom 所以js只能写 表达式

const msg = 'hello react'
root.render(
  <div>
      {/* 这是注释 */}
      {msg}
  </div>

);

2、 jsx中的标签属性 和js关键字冲突 转义成其他属性, 也可以通过解构添加属性

const attr = {
  a: 'a',
  b: 'b'
}
root.render(
    <div>
      <label htmlFor="b" className='xx'>
         点我点我
        <input type="checkbox" name="" id="b" />
        <button {...attr}>看我属性</button>
      </label>
    </div>
);

3、 jsx只能放在一个根标签中[仅能有一个根标签]

jsx的原理

在js中写的jsx标签, react在运行之前会自动编译 过程: 分析标签 -> 调用React.createElement() [相当于vue中的h渲染函数] -> 编译成虚拟dom对象

<div className="box" id="container">
  <p className="op">这是p</p>
  <span>这是span</span>
  这是文本
</div>

// 运行时会自动编译成如下代码
React.createElement('div', {className: 'box', id:'container'}, [
  React.createElement('p', {className: 'op'}, ['这是p']),
  React.createElement('span', {}, ['这是span']),
  '这是文本'
])

一、react两种组件 [ 函数式组件、class组件 ]

1、 函数式组件:[当作组件使用时首字母必须是大写的]

定义函数式组件

const App = (props) => {
  console.log(props);
  return (
    <div>
      <h1>我是函数式组件</h1>
      {props.title} 
    </div>
  )
}

使用函数式组件, 通过自定义属性传递props, 函数的第一个参数就是props

root.render(
  <App title="hello react"/>
);

2、class组件

应掌握 class的属性、方法、继承、静态属性、this指向问题 tips:在使用jsx时, 相当于将class实例化并且调用了render函数。可以通过自定义属性传递参数,参数会挂载在实例上的props中 创建一个class组件

import React, { Component } from 'react'
class Index extends Component {
  render() {
    console.log(this);
    return (
      <div>
        <h1>
          hello world
        </h1>
        {this.props.title}
      </div>
      
    )
  }
}
export default Index

使用class组件

root.render(
  <Index title="hello react"/>
);

3、class组件与 函数式组件对比

1、 没有内部状态 2、 没有生命周期钩子

二、 react的组件样式

1、行内样式 (不推荐)

jsx中的样式需要给一个对象, 连接符去‘-’变大驼峰、 可以省略px单位

 <h1 style={{ color:'red', fontSize: '20' }}>
          hello world
 </h1>

2、组件引入外部样式 (不推荐)

支持文件: css 、 scss、 less文件, 注意:除了css、其他样式均需要安装依赖

// 引入外部css
import './css/style.css'
import './scss/style.scss'

问题: 引入外部文件会影响到全局

3、css模块 (推荐使用)

1、 命名: name.module.css [name对应组件名字]

h1{
    background-color: yellow;
}
.head{
    color: rgb(7, 32, 160);
}
.box{
    width: 200px;
    height: 200px;
    background-color: pink;
    :global{
        .p{
            background-color: blue;
        }
    } 
}

2、使用

// 引入外部css
import styles from './css/TodoList.module.css'
import styles2 from './scss/TodoLists.module.scss'
console.log(styles);
console.log(styles2);
class Index extends Component {
  render() {
    return (
      <div>
        <h1 className={styles.head}>
          hello world
        </h1>
        {/* <h1>111</h1> */}
        <div className={styles2.box}> 
          <p className='p'> 
            {this.props.title}
          </p>
        </div>
      </div>
    )
  }
}

原理: 将原选择器重命名带上一段哈希值保证不会重名

3、总结:

  • a、注意: 标签选择器不会重新编译、还是会影响全局
  • b、嵌套的层级只要将外层重写,内部用global声明不要重写

4、使用插件 [style-components]

三、props校验 [prop-types]

安装三方库

npm install --save prop-types 

使用

import React from 'react';
import PropTypes from 'prop-types';

// 类组件
class MyComponent extends React.Component {
  render() {

  }
}
// 函数式组件
function MyComponent(props) {

}

// 两个组件均可以这样使用
MyComponent.propTypes = {
  // 基础校验规则
  optionalArray: PropTypes.array,
  optionalBigInt: PropTypes.bigint,
  optionalBool: PropTypes.bool,
  optionalFunc: PropTypes.func,
  optionalNumber: PropTypes.number,
  optionalObject: PropTypes.object,
  optionalString: PropTypes.string,
  optionalSymbol: PropTypes.symbol,

  // A React element (ie. <MyComponent />).
  // 直接 { props.xx 即可渲染 }
  optionalElement: PropTypes.element,
  // A React element type (eg. MyComponent).
  // 直接 <props.xx> 渲染
  optionalElementType: PropTypes.elementType,

  // 必须是message的实例
  optionalMessage: PropTypes.instanceOf(Message),
  // 枚举, 必须是其中之一
  optionalEnum: PropTypes.oneOf(['News', 'Photos']),
  // 是多个类型中的一个
  optionalUnion: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
    PropTypes.instanceOf(Message)
  ]),

  // 是某个固定类型数组
  optionalArrayOf: PropTypes.arrayOf(PropTypes.number),
  // 对象属性必须是某个类型
  optionalObjectOf: PropTypes.objectOf(PropTypes.number),

  // 描述对象结构,仅仅要求其中一部分
  optionalObjectWithShape: PropTypes.shape({
    optionalProperty: PropTypes.string,
    requiredProperty: PropTypes.number.isRequired
  }),
  // 描述结构,要求必须和结构一样
  optionalObjectWithStrictShape: PropTypes.exact({
    optionalProperty: PropTypes.string,
    requiredProperty: PropTypes.number.isRequired
  }),
  // 链式调用
  requiredFunc: PropTypes.func.isRequired,
  // 任意类型
  requiredAny: PropTypes.any.isRequired,
};

props的默认值

若父组件没有传递则使用默认值,传递则使用传递的

// 定义静态属性defaultProps即可
TodoHead.defaultProps = {
  d: 'd的默认值'
}

props的children [可以类比vue中的插槽]

父组件可以在 子组件的双标签 中嵌入模板

 <Todo
    a={
        <h1> 我是app中的a </h1>
    }
  >
      <p>这是p</p>
      <p>这是后面的p</p>
  </Todo>

子组件内部可以直接渲染

<div> Todo组件 
    {this.props.a}
    {this.props.children}
</div>

# 四、class组件的state

  • 问题:直接修改state视图不会刷新, 因为react不是mvvm架构
    • 1、 使用this.forceUpdate() [不推荐]
    • 2、 使用setState() [推荐]

函数组件管理状态的属性, 应挂载在实例上.

使用、修改state

修改state使用setState({} , () => {})

import React, { Component } from 'react'
export default class Todo extends Component {
  // state应挂载在实例
  state = {
    a: 10,
    b: true,
    c: [1,2,3,4]
  }
  // 若要传参使用construct, this.state 来定义
  /*   construct(){
    this.state={

    }
  } */
  change = () => {
    // 修改是个异步的, 可以同时修改多个
    this.setState({
      a: '值修改了',
      c: [1,1,1,1]
    })
    console.log(this.state.a);
  }

  render() {
    return (
      <div>
        <h1>todo 组件</h1>
        <button onClick={
          this.change
        }>修改状态</button>
        {this.state.a}
        <ul>
          {
            {/* 访问state数据 */}
            this.state.c.map((item, index) => {
              {/* 遍历需要传递一个key */}
                return <li key={index}>{item}</li>
            })
          }
        </ul>
      </div>
    )
  }
}

2、获取异步修改后的数据[18版本以前在原生事件中同步,现在都是异步]

setState中第二个参数是回调函数用于获取异步修改后最新的数据

  change = () => {
    // 修改是个异步的, 可以同时修改多个
    this.setState({
      a: '值修改了',
      c: [1,1,1,1]
    }, 
    // 回调, 在数据更新视图刷新完成后触发
    () => {console.log(this.state.a,222);})
  }

五、核心概念

1、单向数据流 (不可以修改props)

2、状态提升 (应该明确区分 ui组件 逻辑组件 将状态提升到逻辑组件[父组件]) ---> 内部的弹窗、表单等 状态在子组件处理,通过自定义事件传递给父组件处理业务

3、受控组件/非受控组件

六、react的和合成事件

合成事件: 集成原生事件、绑定 react 方法

语法: on+原生事件 [onClick/onChange...]

<button onClick={事件函数}>点击</button>

1、在class组件中如何定义 事件函数 [主要考虑this指向问题]

  • 1、在行内直接用箭头函数 [业务代码嵌入了视图,不推荐使用]
import React, { Component } from 'react'
export default class Todo extends Component {
  // state应挂载在实例
  state = {
    msg: 'hello react'
  }
  render() {
    return (
      <div>
        <h1>todo 组件</h1>
        <button onClick={
          // 行内定义箭头函数
          () => { this.setState({ msg: '值修改了' })}
        }>修改状态</button>
        {this.state.msg}
      </div>
    )
  }
}
  • 2、原型上定义方法,在行内通过bind改变this指向 [render触发会导致bind频繁返回新函数,不推荐]
changeMsg(){
    this.setState({ msg: '值修改了' })
  }
render() {
    return (
      <div>
        <h1>todo 组件</h1>
        <button onClick={
          this.changeMsg.bind(this)
        }>修改状态</button>
        {this.state.msg}
      </div>
    )
  }
  • 3、在construct上修改this [只会触发一次bind、但是有两个方法、绑定的是bind返回的函数,不推荐]
export default class Todo extends Component {

  constructor(){
    super()
    this.state = {
      msg: 'hello react'
    }
    this.changeMsg = this.changeMsg.bind(this)
  }
  changeMsg(){
    this.setState({
      msg: '值改变了'
    })
  }  
  render() {
    return (
      <div>
        <h1>todo 组件</h1>
        <button onClick={
          this.changeMsg
        }>修改状态</button>
        {this.state.msg}
      </div>
    )
  }
}
  • 最推荐:变量=箭头函数形式 这种会将changeMsg挂载在实例上面并且this永远指向实例
import React, { Component } from 'react'
export default class Todo extends Component {
  state = {
    msg : 'hello react'
  }
  changeState = () => {
    this.setState({
      msg: '值改变了'
    })
  }
  render() {
    return (
      <div>
        <h1>todo 组件</h1>
        <button onClick={
         this.changeState
        }>修改状态</button>
        {this.state.msg}
      </div>
    )
  }
}

七、事件对象

1、 事件函数的第一个参数就是事件对

  e.target: 获取事件dom
  e.stopPropagation(); :阻止冒泡
  e.preventDefault(); :阻止默认事件

2、 事件函数传参

将箭头函数作为事件函数、将fn在箭头函数中调用即可实现传参

export default class Todo extends Component {
  render() {
    return (
      <div className='box1' onClick={this.fn1}>
        1
          <div className='box2' onClick={this.fn2}>
            2
              <div className='box3' onClick={
                (e) => {
                  this.fn3(e,3)
                }
              }>
                3
              </div>
          </div>
      </div>
    )
  }
  fn3 = (e, a) => {
    console.log(3);
    // 组织冒泡
    e.stopPropagation();
    console.log(a);
  }

八、react渲染视图

理解jsx语法、标签就是对象

1、 条件渲染

使用三目运算符

<>
    <button onClick={() => {
      this.setState({
        isShow: !this.state.isShow
      })
    }}>{this.state.isShow ? '隱藏' : '显示'}</button>

    {
      this.state.isShow ? <h1 style={{textAlign: 'center'}}>Todo</h1> : null
    }
</>

2、循环渲染

给每一项处理加上标签渲染即可, 注意:循环需要一个独一无二的key

import React, { Component } from 'react'

export default class Todo extends Component {
  state = {
    arr: ['a','b','c','d']
  }
  render() {
    return (
      <>
        {
          this.state.arr.map((item,index) => {
            return <li key={index}>{item}</li>
          })
        }
      </>
    )
  }
}

3、富文本渲染

使用react提供的属性

import React, { Component } from 'react'
export default class Todo extends Component {
  state = {
    context: '<h1 style="color:red">富文本数据</h1>' 
  }
  render() {
    return (
      <>
        {
          this.state.context
        }
        <div dangerouslySetInnerHTML={{__html: this.state.context}}></div>
      </>
    )
  }
}

九、容器组件[Fragment / 或者<></> 空标签]

主要用于解决jsx自动生成的dom嵌套太深的问题

import React, { Component, Fragment } from 'react'
export default class Todo extends Component {
  render() {
    return (
      <Fragment>
        <h1>我是h1</h1>
      </Fragment>
/*       <>
        <h1>我是h1</h1>
      </> */
    )
  }
}

十、表单值的绑定

1、单向绑定 [使用defaultValue]

import React, { Component } from 'react'
export default class Todo extends Component {
  state = {
    msg: 'hello react',
    isChecked: true
  }
  render() {
    return (
      <>
          <input type="text" defaultValue={this.state.msg} />
          <hr />
          {this.state.msg}
          <hr />
          <input type="checkbox" defaultChecked={this.state.isChecked}/> {this.state.isChecked ? '差勤' : '正忙'}
      </>
    )
  }
}

2、双向绑定 [使用value、合成事件]

import React, { Component } from 'react'
export default class Todo extends Component {
  state = {
    msg: 'hello react',
    isChecked: true
  }
  render() {
    return (
      <>
          <input type="text" value={this.state.msg} onChange={this.changeMsg}/>
          <hr />
          {this.state.msg}
          <hr />
          <input type="checkbox" checked={this.state.isChecked} onChange={this.changeChecked}/>
          {this.state.isChecked ? '选中' : '未选'}
      </>
    )
  }
  changeMsg = (e) => {
    this.setState({
      msg: e.target.value
    }, () => {console.log(this.state.msg);})
  }
  changeChecked = (e) => {
    this.setState({
      isChecked: e.target.checked
    }, ()=>{console.log(this.state.isChecked);});
  }
}


十一、react的组件通信

1、父子组件通信

1、 父向子通信:通过自定义属性 在props中接收 父组件

export default class App extends Component {
  state = {
    A_msg: '我是父组件数据'
  }
  render() {
    return (
      <>
        <Todo a={this.state.A_msg}>
        </Todo> 
      </>
    )
  }
}

子组件

export default class Todo extends Component {
  render() {
    return (
      <>
         子组件:{ this.props.a }
      </>
    )
  } 
}

2、 子向父通信:父组件定义函数 props接受父组件传递来的函数, 在事件中触发并且传递参数 父组件

export default class App extends Component {
  state = {
    msg: ''
  }
  biubiu = (msg) => {
    console.log('我收到了子组件的消息'+msg);
    this.setState({
      msg: msg
    })
  }
  render() {
    return (
      <>
        父组件:{ this.state.msg }
        <hr />
        <Todo emit={this.biubiu}>
        </Todo> 
      </>
    )
  }
}

子组件

export default class Todo extends Component {
  state = {
    msg: '我是子组件消息'
  }
  render() {
    return (
      <>
         <h1>我是todo组件</h1>
         <button onClick={() => {
            this.props.emit(this.state.msg)
         }} >点击向父父组件发送信息</button>
         { this.props.a }
      </>
    )
  } 
}
Todo.propTypes = {
    emit: PropTypes.func.isRequired
}

2、兄弟组件通信 [使用PubSub三方插件,原理发布订阅]

  npm  i  PubSub -D

itemA

import pubsub from './utils/PubSub'
export default class itemA extends Component {

  state = {
    msg : 'this is A msg'
  }
  render() {
    return (
      <>
        <div>itemA</div>
        <button onClick={() => {pubsub.publish('sendMsg', this.state.msg)}}>点击向B发送数据</button>
      </>
    )
  }
}

itemB

import pubsub from './utils/PubSub'

export default class itemB extends Component {

  state = {
    msg: '',

  }  
  render() {
    return (
      <>
        <div>itemB</div>
        兄弟组件的数据:{this.state.msg}
      </>
    )
  }
  componentDidMount() {
    pubsub.subscribe('sendMsg', (data) => {
      console.log('我收到了兄弟组件的来信:'+data);
      this.setState({
        msg: data
      })
    })
  }
}

十二、 AntDesign组件库 + TodoList案例

官网 安装: 直接引入使用即可

  npm install antd --save

十三、 react组件的生命周期钩子

(网址)[www.yuque.com/wangkai-jjg…]

1、 初始化挂载阶段

  • constructor [类实例化时会调用]
  • static getDerivedStateFromProps
  • render
  • componentDidMount 组件会更新的三个场景:props改变、setState调用、forceUpdate调用

2、 更新阶段

  • static getDerivedStateFromProps
  • shouldComponentUpdate ==> 可以阻止视图更新
  • render
  • componentDidUpdate

3、 卸载阶段

  • componentWillUnmount

生命周期应用场景

常用钩子

1、初始化阶段

  • constructor: 定义 state 给实例初始化
  • componentDidMount: 用于调用组件初始化函数, 获取dom

2、更新阶段

  • componentDidUpdate:可以获取更新后的dom、不推荐使用

3、卸载阶段

  • componentWillUnmount: 用于卸载组件中的全局时间和属性

父组件

export default class App extends Component {
  state = {
    isShow: true,
  }
  render() {
    return (
      <>
        <button onClick={() => {this.setState({isShow: !this.state.isShow})}}>
          {this.state.isShow ? "隐藏" : "显示"}
        </button>
        {
          this.state.isShow 
          &&
          <Todo />
        }
      </>
    )
  }
}

子组件

export default class Todo extends Component {
  constructor() {
    super();
    this.state = {
      todo: [],
      timer: -1
    };
  }

  render() {
    return (
      <>
        <h1>todo</h1>
        <div style={{height: 2000}}>
          {this.state.todo.map((item) => {
            return <div key={item.id}>{item.name}</div>;
          })}
        </div>
      
      </>
    );
  }

  fetchCates = () => {
    axios.get("https://api.it120.cc/conner/cms/category/list").then ((res) => {
      console.log(res);
      if (res.data.code === 0) {
        console.log(res);
        this.setState({
          todo: res.data.data,
        });
      }
    });
  };
  componentDidMount () {
    this.fetchCates();
    window.onscroll = () => {
      console.log('滚动了');
    }
    const  timer =  setInterval(()=>{
      console.log('战斗!');
    },2000)

    this.setState({
      timer
    })
  }
  componentWillUnmount () {
    console.log('组件卸载!');
    window.onscroll = null;
    clearInterval(this.state.timer)
  }
}

进阶钩子

  • static getDerivedStateFromProps 始终在render前触发, 返回对象的属性会自动添加到组件的state上
export default class Todo extends Component {
  state = {
    msg: 'hello',
  }
  static getDerivedStateFromProps(props, state) {
      /* 
        state: 组件的 默认state
        props:组件的 props
        返回对象的属性会自动添加到组件的state上
      */
      return {
        doubleNum : props.num * 2,
        reversMsg: state.msg.split('').reverse().join('')
      }
  }
  render() {
    console.log(this.state);
    return (
      <div>Todo</div>
    )
  }
}

十四、react中的组件优化

1、react 组件更新机制

祖先组件更新,所有的后代组件都会无条件的随之更新。导致后代组件无意义的re-render

2、组件的更新优化方案

  • 1、使用shouldComponentUpdate钩子阻止后代组件更新 [不推荐]
  shouldComponentUpdate(nextProps, nextState){
    /* 
        参数1: 如果更新 更新后最新的props 、 this.props是更新前的props
        参数2: 如果更新 更新后最新的state 、 this.state是更新前的state
    */
    // 判断子组件数据发生改变没
    return nextProps.item !== this.props.item
  }
  • 2、让类组件继承PureComponent[推荐]

十五、 react获取dom实例

引入createRef 获取子组件实例 或者 dom实例[在类组件中、将dom对象挂载在组件实例]

import React, { Component, createRef } from 'react'
export default class Todo extends Component {
  constructor(){
    super()
    this.btn = createRef(null)
    this.item = createRef(null)
  }

  render() {
    return (
      <>
        <div ref={this.item}>Todo</div>
        <button ref={this.btn}>按钮</button>
      </>
    )
  }
  componentDidMount(){
    console.log(this.btn.current);
    console.log(this.item);
  }
}

十六、 context 上下文

实现了 不需要通过祖先组件层层传递props就可以 跨组件通信

1、 context 的基础使用

1、 引入创建一个context

import { createContext } from "react";
// 实例化
const context = createContext();
/* 
    context对象下有两个属性 (组件)
    Provider 数据提供
        通过value属性提供数据, 数据只能由后代组件 通过Consumer获取
    Consumer 数据消解
*/
const { Provider, Consumer } = context;
export {
    Provider,
    Consumer,
    context
}

2、 让Provider 作为根节点、 使用value提供数据

import { Provider } from './context/index'
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
const data = {
  a: 10,
  b: 20
}
root.render(
  <Provider value={data}>
    <App />
  </Provider>
);

3、 使用Customer 接收数据

import React, { Component } from 'react'
import { Consumer } from '../context'
export default class A extends Component {
  render() {
    return (
        <Consumer>
            {
                (data) => {
                    console.log(data.a);
                    return <div>{data.a}<h2>A组件</h2></div>
                }
            }
        </Consumer>
    )
  }
}

2、 context 的进阶使用 [对Provider提供的数据修改、并且刷新试图]

1、 将context的Provider放在一个组件中 context

import React, { Component, createContext } from 'react'

const context = createContext()
const { Provider, Consumer } = context;
class ContextProvider extends Component {
    state = {
        a: 10,
        b: 20
    }
    render() {
        return (
            <Provider value={{ ...this.state, changea: this.changea }}>
                {this.props.children}
            </Provider>
        )
    }
    changea = (n) => {
        this.setState({
            a: n
        }, () => { console.log(this.state); })
    }
}

export {
    Consumer,
    ContextProvider,
    context
}

2、 将App使用ContextProvider包裹、使用this.props.children嵌入Provider中 入口

import {ContextProvider} from './context'
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <ContextProvider>
    <App />
  </ContextProvider>
);

3、在后代组件接收并且调用根组件传递的数据函数 A.jsx

import React, { Component } from "react";
import { Consumer } from "../context";
export default class A extends Component {
  render() {
    return (
      <Consumer>
        {(data) => {
          console.log(data.a);
          return (
            <div>
              <button onClick={() => {data.changea(101)}}>修改a</button>
              {data.a}
              <h2>A组件</h2>
            </div>
          );
        }}
      </Consumer>
    );
  }
}

十七、 高阶组件 [hoc] (high order component)

补充:hook函数 => 必须以useXxx命名, 在函数组件内部使用。

原理 :柯里化函数 [最外层的有名字的函数称为高阶函数] 本质就是一个高阶函数, 返回一个组件, 接收参数(也是一个组件)。 返回一个新的被修饰后的组件[可以给被修饰的组件加其他组件和props]。

注意:高阶组件以withXxx命名,

  fn()()

定义:

import React  from 'react'
const withComponent = (Com) => {
   return (props) => {
    return(
      <>
          <h2>i am header</h2>
            <Com {...props}/>
          <h2>i am footer</h2>
      </>
    )  
  }
}
export default  withComponent

使用

import React from 'react'
export default function A(props) {
  console.log(props);
  return (
    <div>A</div>
  )
}

---------函数式组件---------

十八、 react的一些hook函数

[16.8版本]:推出了 react-hooks 目的是解决函数式组件的缺陷

要求: 1、命名要求 ==> 必须以useXxx开头 2、必须在函数式组件内部使用

1、 useState

语法: let [值(名称), 修改值的方法] = useState(初始值) 作用: 用于将数据更改后使视图刷新 注意: 该更改为异步操作无法直接获取到更改后的值!. 使用:

import React from 'react'
import { useState } from 'react'
export default function App() {
  let [a, setA] = useState(10)
  const addNum = () => {
    setA(++a)
    console.log(a);
  }

  let [obj, setObj] = useState({b: 20})
  const changeObj = () => {
    obj.b = 30
    setObj(obj)
    console.log(obj.b);
  }
  return (
    <div>
      {a}
      <button onClick={addNum}>+</button>
      <hr />
      {obj.b}
      <button onClick={changeObj}>修改</button>
    </div>
  )
}

2、 useEffect

语法: useEffect(() => {} , [依赖])

作用:

1、 若直接写回调、相当于 componentDidMount 和 componentDidUpdate 触发时机, 但是在此钩子没法调用useState, 否则死循环。

useEffect(() => {})

2、 给一个依赖, 当依赖改变才会触更新, 可以获取useState的set修改后数据和dom。 相当于立即侦听的侦听器。

useEffect(() => {} , [依赖])

3、 模拟生命周期的钩子:将依赖设置为一个不会更改的值 比如:空

   useEffect(() => {} , []) 

4、 模拟卸载前的钩子:

    useEffect(() => {
      return () => {
        // 卸载前的钩子
      }
    } , [])

注意: 还有个相同作用的useLayoutEffect, 知道即可,一般不使用

3、useContext

1、 引入创建一个context

import { createContext } from "react";
// 实例化
const context = createContext();
/* 
    context对象下有两个属性 (组件)
    Provider 数据提供
        通过value属性提供数据, 数据只能由后代组件 通过Consumer获取
    Consumer 数据消解
*/
const { Provider, Consumer } = context;
export {
    Provider,
    Consumer,
    context
}

2、 让Provider 作为根节点、 使用value提供数据

import { Provider } from './context/index'
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
const data = {
  a: 10,
  b: 20
}
root.render(
  <Provider value={data}>
    <App />
  </Provider>
);

3、接收使用

import React, { useContext } from 'react'
import { context } from "./context"
export default function App() {
  const data = useContext(context)
  return (
    <div>{data.a}</div>
  )
}

4、useMemo [类似计算属性]

作用: 根据依赖 进行计算 得到新的状态, 多次使用或者组件刷新不会重新调用、只有以来改变才会重新调用

import React, { useMemo, useState } from 'react'
export default function App() {
  let [num1 , setNum1] = useState(10)
  let [num2 , setNum2] = useState(20)

  const doubleNum = useMemo(() => {
    console.log('触发了');
    return num1 * 2
  }, [num1])

  return (
    <div>
      {num1} | {doubleNum} {doubleNum}
      <button onClick={() => {setNum1(num1+1)}}>+</button>
      <hr />
      {num2}
      <button onClick={() => {setNum2(num2+1)}}>+</button>
    </div>
  )
}

5、memo高阶组件

作用: 解决子组件跟随父组件无意义的刷新问题. 只有当父组件传递的props变化才更新. 父组件

import React, { useMemo, useState } from 'react'
import Children from './children'
export default function App() {
  let [num1 , setNum1] = useState(10)
  let [num2 , setNum2] = useState(20)

  const doubleNum = useMemo(() => {
    console.log('触发了');
    return num1 * 2
  }, [num1])

  return (
    <div>
      {num1} | {doubleNum} {doubleNum}
      <button onClick={() => {setNum1(num1+1)}}>+</button>
      <hr />
      {num2}
      <button onClick={() => {setNum2(num2+1)}}>+</button>
      <hr />
      <Children num2 = {num2}/>
    </div>
  )
}

子组件

import React, { memo } from 'react'
export default memo (function children(props) {
  console.log('子组件触发');
  return (
    <div>children {props.num2}</div>
  )
})

6、useCallback

作用: 用于避免某个函数多此重新触发, 依赖不改变不重新触发。

 const addArr = useCallback( () => {
    setArr([...arr, arr.length+1])
 }, [arr])

7、useRef

作用: 1、获取自身的dom标签 2、获取class子组件的实例 3、获取函数式子组件的内部dom标签 [需要配合forwardRef高阶组件使用] 父组件

import React, { useRef, useEffect } from "react";
import Children from "./children";
import Children2 from "./children2";
export default function App() {
  const btn = useRef(null);
  const children2 = useRef(null)
  const children = useRef(null)
  // 初始胡钩子
  useEffect(() => {
    console.log(btn.current);
    console.log(children2.current);
    console.log(children.current);
  }, [])
  return (
    <div>
      <button ref={btn}>按钮</button>
      {/* 函数式组件 */}
      <Children ref={children}/>
      {/* 类组件 */}
      <Children2 ref={children2}/>
    </div>
  );
}

class子组件

import React, { Component } from 'react'
export default class children2 extends Component {
    state = {
        a:1
    }
  render() {
    return (
      <div>children2</div>
    )
  }
}

函数式组件

import React from 'react'
import { forwardRef } from 'react'
// ref传递的属性当作第二个参数, 转发传递给内部的dom
export default forwardRef(function children(props, children) {
  return (
    <div ref={children}>children</div>
  )
})

十八、react的路由

(6.x)[reactrouter.com/en/6.15.0] => 全面拥抱函数式组件 万物皆组件: 安装:

  react-router   // 核心语法包
  react-router-dom  // b/s 浏览器端
  react-router-native  // c/s
  npm i react-router-dom -S

1、路由模式:

1、 HashRouter 2、 BrowserRouter

2、基础语法:

注意: 必须将路由包裹根组件才可以生效

import { HashRouter } from 'react-router-dom';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <HashRouter>
    <App />
  </HashRouter>
);

定义路由组件:[注意:Route必须包含在Routes中]




import {Route , Routes} from 'react-router-dom'
import Home from './pages/Home'
import NewsPage from './pages/NewsPage'
import NotFound from './pages/NotFound'
export default function App() {
  return (
    <>
        <Routes>
          <Route path='/' element={<Home/>}></Route>
          <Route path='/news' element={<NewsPage/>}></Route>
          <Route path='/404' element={<NotFound/>}></Route>
        </Routes>
    </>
  )
}

3、路由导航组件

1、 link

  to: 定义跳转的路径 
    - 可以是 '字符串' 、 对象 { pathname: '/' ,  }
  replace: 布尔值、跳转会覆盖历史记录
  state: 用于跳转传参

缺点: 没有高亮样式处理

    <Link to="/">首页</Link>
    <Link to={{pathname : '/news'}}>新闻</Link>
    <Link to="/about" replace>我们</Link>

2、NavLink [了解即可] 作用: 在Link的基础上、增加了高量样式

  • 默认有高亮类
  • 可以通过回调函数自定义高亮类
  • 可以使用插槽来自定义导航渲染的标签
  <NavLink to="/">首页</NavLink>
  <NavLink to={{pathname : '/news'}}>新闻</NavLink>
  <NavLink to="/about" replace>
    <button>我们</button>
  </NavLink>

4、路由重定向[Navigate] 和 404

1、Navigate: 出现就会跳转、所以需要一个条件

  to:跳转路径
  replace:覆盖跳转记录
    <Routes>
      <Route path='/home' element={<Home/>}></Route>
      <Route path='/news' element={<NewsPage/>}></Route>
      <Route path='/about' element={<NotFound/>}></Route>
      <Route path='/' element={<Navigate to="/home" />}></Route>
    </Routes>

2、404 在路由中 * 的优先级最低

   <Route path='/about' element={<NotFound/>}></Route>

## 5、嵌套路由 总结:

  • 1、子路由的Route 可以直接嵌套写在父路由的Route中, 且可以简写
  • 2、子路由的导航可以在父路由的组件中定义、也可以简写、并且为子路由的出口 主路由
import React from 'react'
import {Route , Routes, Link, NavLink, Navigate} from 'react-router-dom'
import Home from './pages/Home'
import NewsPage from './pages/NewsPage'
import About from './pages/About'
import NotFound from './pages/NotFount'
import Native from './pages/NewsPage/components/Native'
import Abroad from './pages/NewsPage/components/Abroad'
export default function App() {
 return (
   <>
       <NavLink to="/">首页</NavLink>
       <NavLink to={{pathname : '/news'}}>新闻</NavLink>
       <NavLink to="/about" replace>
         <button>我们</button>
       </NavLink>
       <Routes>
         <Route path='/home' element={<Home/>}></Route>
         <Route path='/news' element={<NewsPage/>}>
             {/* 可以不带 / 简写, react会自动不全 */}
             <Route path='native' element={<Native/>}>本地新闻</Route>
             <Route path='/news/abroad' element={<Abroad/>}>本地新闻</Route>
             <Route path='/news' element={<Navigate to="/news/native" replace/>}></Route>
         </Route>
         <Route path='/about' element={<About/>}></Route>
         <Route path='*' element={<NotFound/>}></Route>
         <Route path='/' element={<Navigate to="/home" />}></Route>
       </Routes>
   </>
 )
}

子路由

import React from 'react'
import { Outlet, Link } from 'react-router-dom'
export default function NewsPage() {
  return (
    <>
      <h1>新闻页</h1>
      <Link to="abroad"> 海外 </Link>
      <Link to="native"> 本地 </Link>
      <hr />
      {/* 这是子路由的出口组件 */}
      <Outlet />
    </>
  )
}

6、react-router 常见的hook函数 [编程式导航]

1、 useLocation 作用: 获取当前路由的静态信息对象


  const location = useLocation();

2、useNavigate 作用: 跳转路由、路由传参

  const navigate = useNavigate();
  // 操作历史记录
  <button onClick={() => navigate(0)}>刷新</button>
  <br />
  // 跳转路由
  <button onClick={() => navigate('/home')}>首页</button>
  // 覆盖跳转
  <button onClick={() => navigate('/news')} replace>新闻页</button>
  // 跳转传参
  <button onClick={() => navigate('/about', {state: {a:10, b:20}})}>关于页</button>

7、路由跳转传参

1、 动态路由传参 特点:显示传参、刷新不丢失 定义:

  <Route path='/about/:id' element={<About/>}></Route>

跳转:

  <button onClick={() => navigate('/about/10086', {state: {a:10, b:20}})}>关于页</button>

获取:使用[useParams 钩子]

  const param = useParams()
  console.log(param.id);

2、state 传参 注意:若state传递为空、react会默认为null 特点:隐式传参、数据刷新不丢失 跳转:

  // 编程式跳转
  <button onClick={() => navigate('/about', {state: {a:10, b:20}})}>关于页</button>
  // 导航组件
  <Link to="/about" state={{a:10, b: 0}}></Link>

获取:使用[useParams 钩子]

  // 编程式 、 导航式
  const location = useLocation()
  console.log(location.state);

3、search传参 跳转:

  <button onClick={() => navigate('/home?page=10&pageSize=7')}>首页</button>

获取:使用[useSearchParams 钩子]

  // 编程式 、 导航式
import React from "react";
import { useSearchParams } from "react-router-dom";
export default function Home() {
  const [searchParams, setSearchParams] = useSearchParams();
  console.log(searchParams.get("page"));
  return (
    <>
      <h1>首页</h1>
      <button onClick={() => {setSearchParams({page:1, pageSize:20})}}>动态设置参数</button>
    </>
  );
}

8、useRoutes、配置型路由

1、新建一个routes文件夹、将路由配置定义在其中的文件内并导出

/* eslint-disable import/no-anonymous-default-export */
import Home from "../pages/Home";
import NewsPage from "../pages/NewsPage";
import About from "../pages/About";
import NotFound from "../pages/NotFount";
import Native from "../pages/NewsPage/components/Native";
import Abroad from "../pages/NewsPage/components/Abroad";
import { element } from "prop-types";
export default [
  {
    path: "/home",
    element: <Home />,
  },
  {
    path: "/news",
    children: [
      {
        path: 'native',
        element: <Native/>
      },
      {
        path: 'abroad',
        element: <Abroad/>
      }
    ],
    element: <NewsPage />,
  },
  {
    path: "/about",
    element: <About />,
  },
  {
    path: "*",
    element: <NotFound />,
  }
]

2、使用 引入useRoutes 、 传入routes的配置文件即可

import React from 'react'
import { Link, useNavigate, useRoutes} from 'react-router-dom'
import routesConfig from './routes'
export default function App() {
  const navigate = useNavigate()
  const routes = useRoutes(routesConfig)
  return (
    <>
        <button onClick={() => navigate(0)}>刷新</button>
        <br />
        <button onClick={() => navigate('/home?page=10&pageSize=7')}>首页</button>
        <button onClick={() => navigate('/news')}>新闻页</button>
        {/* <button onClick={() => navigate('/about/10086', {state: {a:10, b:20}})}>关于页</button> */}
        <Link to="/about" state={{a:101, b:10}}>
          <button>关于</button>
        </Link>

        {
          routes
        }
    </>
  )
}