react入门笔记

react

React由Facebook(现在叫 meta) 的软件工程师Jordan Walke创建。2013年的时候在社区开源。那么react是什么呢?React是一个把数据渲染为HTML视图的开源JavaScript 库 [ 视图层框架 ] 。React为程序员提供了一种子组件不能直接影响外层组件的模型 [ 单向数据流 ],数据改变时会对HTML文档进行有效的更新。

特点:

  • 声明式开发
  • 组件化
  • 跨平台
  • 虚拟dom fiber
  • jsx
  • 单向数据流

create-react-app

react脚手架

、基于webpack 构建 react 工程化环境 简称 cra

js 复制代码
npm i create-react-app -g
create-react-app 项目名 // 创建项目

jsx

xml in js

xml 复制代码
<student>
  <name>小明</name>
  <age>18</age>
  <gender>男</gender>
</student>

react中的jsx, 可以在js中直接 写标签,react会自动转换成 虚拟dom对象结构

数据 驱动框架,一定都会设计 虚拟dom, 是虚拟dom 编写 过于繁琐,vue解决方案设计 sfc 在template标签直接 写 标签,自己自动编译成虚拟dom

react解决方案 在js代码直接 写标签, react会自动将 js标签 编译成虚拟dom

洗脑洗脑洗脑洗脑:

react中 在 js看到任意标签, 理解为这是对象对象对象对象

jsx基础语法

  • 任意一个jsx 必须包裹在 闭合标签
  • 想要在 jsx中 定义 js表达式 就要加 {} 包裹 标签值和属性
  • 注释 {/* 这是jsx注释 */}
  • jsx标签某些属性 如果 和 js关键字冲突, 定义其他语义 映射
    比如 for --> htmlFor class --> className
  • 标签支持 批量 展开 一个对象 自动将对象每个属性转换成标签的属性
jsx 复制代码
  const obj = {
    id: 'box',
    className: 'wrap'
  }
  <div {...obj}>1111</div>

react组件

注意

所有组件 首字母必须大写

文件 后缀名建议 为 jsx

函数式组件

现在是 趋势

  • 定义函数 返回jsx 注意 首字母必须大写 参数 函数是组件的 第一个参数 props接收 自定义属性
jsx 复制代码
import React from "react";
// react函数式组件
const App = (props) => {
  console.log(props);
  return (
    <div>
      <h2>这是函数式组件</h2>
    </div>
  )
}


export default App
  • 调用 以标签形式调用 通过 标签 自定义属性传递props
js 复制代码
 <App title="主标题" subTitle="副标题"/>
 // 相当于调用函数

react vscode 插件

ES7+ React/Redux/React-Native snippets

重要作业 复习 class

定义属性 两种方式

定义方法两种方式

继承

静态属性静态方法

类组件

  • 定义
js 复制代码
import React, { Component } from 'react';

class App extends Component{
  // render方法渲染 虚拟dom 方法 必须有返回值
  render(){
    console.log(this);
    return (
      <div>
        <h2>这是react的class组件</h2>
        {
          this.props.title
        }
        <br />
        {
          this.props.subTitle
        }
      </div>
    )
  }
}
  • 使用组件
js 复制代码
<App title="主标题" subTitle="副标题"/>
// 相当于 new 类  实例调用render 传递 自定义属性 挂载实例的 props属性上

类组件和 函数式组件的不同点:

  • 函数式组件 没有内部状态
  • 函数式组件 没有生命周期

jsx原理

原理:

React 包有一个方法 createElement(类似于vue中的h渲染函数), react会自动分析 jsx 标签的结构,并自动 调用 React.createElement 方法 将 写jsx标签编译成 虚拟dom对象

js 复制代码
React.createElement('div', {
  id: 'box',
  className: 'aaa'
},[
  React.createElement('p', {className:'op'}, ['这是p']),
  React.createElement('span', {className:'span'}, ['这是span']),
  '这是文本内容'
])

react组件的样式

内联

  • style 必须是对象
  • 属性 就是 css样式属性, 注意 连接符 - 去-变驼峰
  • 属性值 可以写表达式
  • 可以省略px单位
jsx 复制代码
      <div style={
        {
          width: 100,
          height: 100+200,
          backgroundColor: 'red'
        }
      }></div>

引入 外部的 样式文件

  • 引入外部的css

  • 使用 css预处理器 sass

    cra 内置 sass 环境

    • 安装sass
    js 复制代码
        npm i sass -D

    注意:

    不管是 css还是外部 sass文件,都没有做任何 作用域限制,作用到全局所有的标签

    选择器编写不要 污染

css module

CSS Modules 通过对 CSS 类名重命名,保证每个类名的唯一性,从而避免样式冲突的问题。也就是说:所有类名都具有"局部作用域",只在当前组件内部生效。在 React 脚手架中:文件名、类名、hash(随机)三部分,只需要指定类名即可 BEM。

  • 定义 css module 命名方式 文件名.module.css
css 复制代码
/*app.module.css*/ 
.box{
  width: 200px;
  height: 200px;
  background-color: #d4ac33;
}
.box2{
  width: 200px;
  height: 200px;
  background-color: #2724d5;
}
  • 引入styles
js 复制代码
import styles from './app.module.css'
/* 
  {
    box: '编译后类名',
    box2: '编译后类名'
  }
*/

 <div className={styles.box}></div>
 <div className={styles.box2}></div>
  • scss 使用 css module

    • 定义 xxx.module.scss
    scss 复制代码
      .box{
        width: 200px;
        height: 200px;
        background-color: #cb6d6d;
        .a{
          width: 100px;
          height: 100px;
          background-color: #c6d0af;
        }
      }
    • 引入样式
    jsx 复制代码
      import styles from './xxx.module.scss'
      // 编译成
      {
        box: '编译后类名',
        a: '编译后类名'
      }
    
      <div className={styles.box}>
        <div className={styles.a}></div>
      </div>

    scss 嵌套规则 编译成平行

    定义:global即可嵌套

    scss 复制代码
      .box2{
        width: 200px;
        height: 200px;
        background-color: #cb6d6d;
        :global{
          .a{
            width: 100px;
            height: 100px;
            background-color: #c6d0af;
            .b{
              color: red;
            }
          }
        }
      }

    使用时

    jsx 复制代码
      <div className={styles.box2}>
          <div className="a">
            <div className="b">aaaa</div>
          </div>
        </div>

css in js

理念:万物皆组件

styled-components

原理:

通过样式组件 定义 样式和结构

  • 安装
js 复制代码
npm i styled-components -S
  • 基础使用
js 复制代码
import styled, {keyframes} from 'styled-components';
// 定义样式组件
/* 
  编译成一个组件 Box 内容就是 div标签,且具有如下的样式
*/
const Box = styled.div`
  width: 200px;
  height: 200px;
  background: red;
`
// 内容和选择器的嵌套
const Box2 = styled.div`
  width: 200px;
  height: 200px;
  background: red;
  p{
    color: blue;
  }
  span{
    color: green;
    &:hover{
      color: yellow;
    }
  }
`
// 继承
const Box3 = styled(Box)`
  border: 5px solid #11ee56;
`

// 传递props
const Box4 = styled.div`
  width: 200px;
  height: 200px;
  background: ${props => props.bgc?props.bgc:'#dc6666' };
`;
// 定义关键帧
const move = keyframes`
  0% {
    transform: rotate(45deg);
  }
  100% {
    transform: rotate(-45deg);
  }
`
const Box5 = styled.div`
  width: 10px;
  height: 200px;
  background: red;
  margin:  50px auto;
  transform-origin: center bottom;
  animation: ${move} 200ms infinite alternate linear;
`
export {
  Box,
  Box2,
  Box3,
  Box4,
  Box5
}

通过 prop-types 实现 react组件 props的类型校验

  • 安装
js 复制代码
npm i prop-types -S
  • 使用
js 复制代码
// 1 引入 PropTypes
import PropTypes from 'prop-types'
class Todo extends Component{

}
// 或者 函数式组件
const Todo = (props) => {

}
// 定义函数 静态属性
Todo.propTypes = {
  // 一下是常用类型校验
  a: PropTypes.array,
  b: PropTypes.bigint,
  c: PropTypes.bool,
  d: PropTypes.func,
  e: PropTypes.number,
  f: PropTypes.object,
  g: PropTypes.string,
  h: PropTypes.symbol,
  // 是react组件 传递 需要加标签 实例化后<Todo/> 使用时 {props.i}
  i: PropTypes.element,
  // 是react组件 传递  使用时 <props.j/>
  j: PropTypes.elementType,
  // 必须是 Message的实例 
  k: PropTypes.instanceOf(Message),
  // 枚举 值 必须是给定 多个值中的一个
  o: PropTypes.oneOf(['News', 'Photos']),
  // 类型必须是 给定多个类型中的一个
  p: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
    PropTypes.instanceOf(Message)
  ]),

  // 固定类型的数组  比如 是 number数组
  q: PropTypes.arrayOf(PropTypes.number),

  // 对象 且属性值必须是固定类型
  r: PropTypes.objectOf(PropTypes.number),  
  // 描述的对象结构, 对象 如下属性必须符合要求(对于其他属性没有做要求)
  s: PropTypes.shape({
    optionalProperty: PropTypes.string,
    requiredProperty: PropTypes.number.isRequired
  }),

  // 描述对象结构, 对象必须 只有 如下 属性且必须满足 类型要求
  t: PropTypes.exact({
    optionalProperty: PropTypes.string,
    requiredProperty: PropTypes.number.isRequired
  }),
  // 链式 调用 要求类型 且必传
  u: PropTypes.func.isRequired,
}

props的默认值

直接定义组件静态属性 defaultProps 中定义默认值即可

js 复制代码
class Todo extends Component {}
const Todo = (props) => {}
Todo.defaultProps = {}

props.children

类似于vue slot 实现,标签在使用时 父组件传递一些视图 模板给子组件

  • 父组件中 子组件标签 嵌套jsx 传递内容
jsx 复制代码
      <Todo>
        <div>
          <h2>哈哈哈</h2>
          <h3>嘿嘿嘿</h3>
        </div>
      </Todo>
  • 子组件中 props的children 获取传递的jsx
jsx 复制代码
const Todo = (props) => {
  return (
    <div>
      {props.children}
    </div>
  )
}

class组件

state

注意:函数式组件没有内部状态管理

state管理组件 内部状态

语法:

直接定义 实例的state属性,在属性管理数据即可

  • 给实例定义 state属性
js 复制代码
class Todo extends Component {
  // 外部直接定义
  state = {
    msg: '这是实例自己的状态'
  };
  // 在constructor中定义
  constructor(){
    super();
    this.state = {
      msg: '这是实例自己的状态',
      isBeauty: true
    }
  };
}
  • 修改state

    注意:

    react 不是mvvm框架, 不能直接修改state, 对于state 父类原型 提供了 两个方法让视图刷新

    • 直接修改state 调用 forceUpdate 强烈不推荐

    • 使用setState 修改状态
      1 传对象

      js 复制代码
        this.setState({
          msg: '修改的值',
          isBeauty: !this.state.isBeauty
        })

      2 传函数,函数返回值中修改

      js 复制代码
        this.setState((state, props) => {
          return {
            msg: '这是修改后的值',
            isBeauty: !state.isBeauty
          }
        })

    问题?

    setState修改数据后 会产生副作用(再次调用render,生成组件新的虚拟dom,比较新老dom更新真实dom), setState设计成了 异步的 提高代码执行效率

    数据修改后,无法 直接获取 修改后的数据和视图的

    如何获取修改后最新的数据和dom react 给setState设计了回调, 在数据修改后,且视图更新完成后触发

    js 复制代码
      this.setState({}, () => {
        // 在这里可以拿到修改后最新的数据和dom
      })

react事件绑定

react合成事件

on事件首字母大写

onClick onMouseover onMousedown

绑定合成事件语法

jsx 复制代码
<button onClick={事件函数}>按钮</button>

注意:

事件函数不能加括号

class组件 绑定事件函数的四种方式

  • 行内定义箭头函数
    不建议: 造成业务嵌套 视图模板中
jsx 复制代码
class Todo extends Component {
  constructor(){
    super();
    this.state = {
      isBeauty: true
    };
  };
  render() {
    return (
      <div>
       <button onClick={
          () => {
            this.setState({
              isBeauty: !this.state.isBeauty
            })
          }
        }>按钮</button> 
      
        {this.state.isBeauty ? '美女啊': '你真善良'}
      </div>
    )
  };
}
  • 方法定义 原型上, 行内通过bind 改变this指向
    不推荐: render 会多次触发 造成多次bind
jsx 复制代码
class Todo extends Component {
  constructor(){
    super();
    this.state = {
      isBeauty: true
    };
  };
  render() {
    return (
      <div>
       <button onClick={
          this.clickBtn.bind(this)
        }>按钮</button> 
        {this.state.isBeauty ? '美女啊': '你真善良'}
      </div>
    )
  };
 clickBtn(){
    this.setState({
      isBeauty: !this.state.isBeauty
    })
  } 
 
}
  • 方法定义原型上, 在constructor 给实例定义 同名方法, 值为 原型方法 bind返回的函数
    较为推荐
jsx 复制代码
class Todo extends Component {
  constructor(){
    super();
    this.state = {
      isBeauty: true
    };
    this.clickBtn = this.clickBtn.bind(this);
  };
  render() {
    return (
      <div>
        <button onClick={
          this.clickBtn
        }>按钮</button>
        {this.state.isBeauty ? '美女啊': '你真善良'}
      </div>
    )
  };
 clickBtn(){
    this.setState({
      isBeauty: !this.state.isBeauty
    })
  } 
}
  • 在实例上定义方法 使用箭头函数形式
    class语法: 如果实例上 方法 通过箭头函数定义,class将强行将 方法this 指向实例

推荐写法

jsx 复制代码
class Todo extends Component {
  constructor(){
    super();
    this.state = {
      isBeauty: true
    };
  };
  render() {
    return (
      <div>
        <button onClick={
          this.clickBtn
        }>按钮</button>
        {this.state.isBeauty ? '美女啊': '你真善良'}
      </div>
    )
  };
  clickBtn = () => {
    this.setState({
      isBeauty: !this.state.isBeauty
    })
  }
}

事件对象

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

js 复制代码
e.stopPropagation() // 取消冒泡
e.preventDefault() // 阻止默认事件
e.target // 获取事件源

事件传参

行内定义箭头函数充当事件 函数, 方法在 箭头函数中调用即可传参

js 复制代码
class Todo extends Component {
  render() {
    return (
      <div>
        <button onClick={
          (e) => {
            this.clickBtn(5, e)
          }
        }>按钮</button>
      </div>
    )
  };
  clickBtn = (n, e) => {
    alert(n)
    e.target.style.background = 'red';
  }
}

react数据渲染

  • react条件渲染
    使用短路或者三目表达式 实现 条件渲染
jsx 复制代码
class Todo extends Component {
  state = {
    isShow: true
  }
  render() {
    return (
      <div>
        <button onClick={
          () => {
            this.setState({
              isShow: !this.state.isShow
            })
          }
        }>{this.state.isShow? '隐藏': '显示'}</button>

        {
          this.state.isShow
          &&
          <div className="box"></div>
        }

        {
          this.state.isShow
          ?
          <div className="box"/>
          :
          <div className="box2"/>
        }
      </div>
    )
  }
}
  • 循环渲染
    使用 数组的map方法
jsx 复制代码
class Todo extends Component {
  state = {
    arr: ['a', 'b', 'c', 'd']
  }
  render() {
    return (
      <div>
          <ul>
            {
              this.state.arr.map((item, index) => {
                return (
                  <li key={index}>
                    {item}
                    {index}
                  </li>
                )
              })
            }
          </ul>
      </div>
    )
  }
}
  • 渲染富文本
jsx 复制代码
<div dangerouslySetInnerHTML={{__html: this.state.content}}></div>
  • 容器组件
    问题?
    jsx语法要求,任意一个jsx 必须 包裹在一个闭合标签, 往往会造成, dom树 层级 过深, react提供容器组件 Fragment 作为 jsx容器 优点, 渲染时不会渲染任何标签
jsx 复制代码
import React, { Fragment } from 'react'
import Todo from './Todo'

export default function App() {
  return (
    <Fragment>
      <Todo />
    </Fragment>
  )
}

也可以使用 空标签充当容器

jsx 复制代码
class Todo extends Component {
  render() {
    return (
      <>
        <h2>这是todo</h2>
      </>
    )
  }
}

表单值得绑定

  • 绑定初始值 (表单控件 初始值 等于 某个 状态,值是可变,变化后不会 改变状态)

    提供了两个属性分别是

    defaultValue

    defaultChecked

    js 复制代码
      class Todo extends Component {
        state = {
          msg: '这是初始值',
          isBeauty: true
        }
        render() {
          return (
            <>
              <input type="text" defaultValue={this.state.msg}/>
              <br />
              {this.state.msg}
              <hr />
              <input type="checkbox" defaultChecked={this.state.isBeauty} />
              <br />
              {
                this.state.isBeauty?'真的':'假的'
              }
            </>
          )
        }
      }
  • 双向绑定

    原理:

    直接将状态绑定 表单控件的 value和checked属性, 添加onChange 事件 在事件函数

    e.target.value e.target.checked 赋值给 绑定state即可

    jsx 复制代码
      class Todo extends Component {
        state = {
          msg: '这是初始值',
          isBeauty: true
        }
        render() {
          return (
            <>
            
              <input type="text" value={this.state.msg} onChange={
                this.handleInput
              }/>
              <br />
              {this.state.msg}
              
              <hr />
              <input type="checkbox" checked={this.state.isBeauty} onChange={
                this.handleChecked
              }/>
              <br />
              {
                this.state.isBeauty
                ?
                '美女你好'
                :
                '姑娘你好'
              }
            </>
          )
        };
        handleInput  =(e) => {
          console.log(e.target.value);
          this.setState({
            msg: e.target.value
          })
        };
        handleChecked = (e) => {
          this.setState({
            isBeauty: e.target.checked
          })
        }
      }

react组件通信方案

  • 父向子 props传递参数

  • 子向父通信

    原理:

    父组件中定义 方法, 通过props 传递给子组件这个方法,子组件 调用 该方法 通过方法参数 可以 传递 子组件数据

    • 父组件
    jsx 复制代码
      class App extends Component {
        state = {
          msg: ''
        }
        render() {
          return (
            <div>
              <h2>父组件</h2>
              {this.state.msg}
              <hr />
              <Todo fn={this.fn}/>
              {/*通过props将父组件方法 传递给子组件*/}
            </div>
          )
        };
        fn = (msg) => {
          // 父组件方法
          alert('我调用了'+ msg)
          this.setState({
            msg
          })
        }
      }
    • 子组件中调用 父组件 通过props 传递方法
    jsx 复制代码
      class Todo extends Component {
        state = {
          msg: '这是子组件的数据'
        }
        render() {
          
          return (
            <div>
              <button onClick={
                () => {
                  this.props.fn(this.state.msg)
                  {/*子组件调用 父组件传递的方法*/}
                }
              }>子向父通信</button>
              <h2>子组件</h2>
            </div>
          )
        }
      }
  • 兄弟组件通信

    可以利用PubSub 发布订阅的 js库进行兄弟组件通信

js 复制代码
npm i PubSub
// 创建实例
const pubsub = new PubSub();
// 兄弟组件1 中 订阅一个消息
pubsub.subscribe('biubiu', (data) => {

})
// 兄弟组件2 中发布该消息
pubsub.publish('biubiu', 携带的数据)

createRef class组件中 定义 ref 转发 dom或子组件实例

jsx 复制代码
import React, { Component, createRef } from 'react'
import Todo from './Todo'


export default class App extends Component {
  constructor(){
    super();
    // 在实例上存储 容器
    this.btnRef = createRef(null);
    this.todoRef = createRef(null);
  };
  render() {
    return (
      <div>
        {/* 挂载容器 将子组件实例dom存储到容器中*/}
        <Todo ref={this.todoRef}/>
        <button ref={this.btnRef}>按钮</button>
      </div>
    )
  };
  componentDidMount(){
    // 在容器current属性中获取
    console.log(this.btnRef.current);
    console.log(this.todoRef.current);
  };
}

五一作业:

回来收两个项目 pc和移动 录制 视频 等

提前学习react后面视频 学习到 react-router之前

class组件 生命周期

初始化

  • constructor
    初始化 创建实例 在这里可以 定义state 初始 给实例挂载
  • static getDerivedStateFromProps
  • render
    生成 组件的虚拟dom视图结构
  • componentDidMount
    真实dom构建完成, 1 组件初始化 数据函数的请求调用 2 任何初始化获取dom操作

更新阶段

  • static getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • componentDidUpdate
    真实dom更新完成,在这里可以获取最新的dom 不建议使用

componentWillUnmount

组件即将卸载

使用场景:

注销一些全局挂载,比如 定时器、 全局事件等

static getDerivedStateFromProps

类似于vue计算属性, 从组件已有的props和state中 派生出新的状态

react 父子组件 更新机制

react中 只要祖先组件更新, 后代默认一定会更新,不管导致 祖先组件更新数据 有没有在 后代组件中使用

问题?

后代组件 无意义的 re-render

如何解决 react 后代组件 因为 祖先组件的 更新 导致 无意义 re-render

  • 使用 生命周期钩子 shouldComopnentUpdate (开发不用、面试要用)
js 复制代码
class TodoItem extends Component {
  shouldComponentUpdate(nextProps, nextState){
    /* 
      函数 在每一次 子组件 更新render前触发, 拦截后代组件更新, return false 子组件 永远不更新
      return true 子组件更新(只要祖先组件更新)

      参数1
        nextProps 如果更新,更新后 最新的props   this.props更新的前组件props
        nextState 更新最新的state this.state 更新的前state
    */
    // console.log(nextProps.item, this.props.item);
    return nextProps.item !== this.props.item
  };
  render() {
    console.log('子组件render');
    return (
      <div>
        <h2>{this.props.item}</h2>
        <button onClick={
          () => {
            this.props.changeMsg(this.props.index)
          }
        }>改变值</button>
      </div>
    )
  }
}

原理:

在每一次子组件更新前,比较新老props和新老state,如果有改变 则 shouldComponentUpdate return true让子组件更新,否则不更新

  • 使用 PureComponent
    子组件 不再继承 Component 使用 PureComponent

原理:

react 自动在 子组件 更新前 ,对于 所有 新老 props和state 进行浅层比较, 有改变 子组件更新没有改变子组件不更新

函数式组件

react函数式组件 问题?

比如 没有 内部状态 没有生命周期钩子

react在 16.8 推出了 react hook函数 解决函数式组件 问题

1 hook函数常见命名是 useXxx (use功能名)

2 hook函数 只能在 函数式组件 内部使用,其他函数 或者 外部无法使用的

useState

解决函数式组件中 没有内部状态的问题

jsx 复制代码
function App() {
  /* 
    useState传入初始值 返回值 是数组 
      1 个 当前值
      2 个 修改值得方法, 方法要求 必须传入一个新值 传入新值时 数据修改视图自动刷新
        主要针对引用类型, 一定要克隆传入
  */
  const [num, setNum] = useState(10);
  const [arr, setArr] = useState([1,2,3,4]);
  return (
    <div>
      <button onClick={
        ()=> {
          setNum(num+1)
        }
      }>+</button>
      {
        num
      }

      <hr />
      <button onClick = {
        () => {
          setArr([
            ...arr,
            arr.length+1
          ])
        }
      }>增加li </button>
      <ul>
        {arr.map(el => (
           <li key={el}>
            {el}
           </li>
        ))}
       
      </ul>
    </div>
  )
}

注意:

useState返回set函数 修改值 一定要传入新值 主要针对引用类型,一定要 克隆后传入新值

useState set函数修改数据,是异步的,且不支持 异步回调

useEffect

  • 解决react 函数式组件 没有生命周期钩子函数的问题 (模拟 生命周期 钩子函数)
  • 监听数据变化, 解决 useState 异步问题

1 默认 会在初始化完成和更新完成都触发 相当于 componentDidMount和 componentDidUpdate

js 复制代码
useEffect(() => {
  // 相当于 componentDidMount和 componentDidUpdate
})

注意: 这种不要用,经常会造成死循环

2 useEffect 定义第二个参数 是数组 数组中定义 更新阶段触发的 依赖

更新阶段只有依赖中 任意一个发生改变时 才触发

功能:

类似 vue中watch(立即触发的watch)

解决 useState set函数修改数据 异步的问题

js 复制代码
useEffect(() => {}, [a,b,c])

3 模拟 初始化完成的 生命周期钩子函数

作用: 类似 componentDidMount vue 的 onMounted

组件初始化 请求函数的调用

第三方插件的初始化

js 复制代码
// 定义空的依赖即可
useEffect(() => {}, [])

4 模拟组件 卸载前的生命周期钩子函数

js 复制代码
useEffect(() => {
  return () => {
    // 返回的函数 会在卸载前触发
  }
}, [])

context

react 提供了 可以 实现 跨层级 组件 数据传递的方案

  • 创建context对象
js 复制代码
import { createContext } from "react";
// 创建context对象
const context = createContext();
/* 
  context对象下 有两个属性 都是react组件
  1 Provider  数据提供者, 通过 value属性挂载公共的数据 只能有 Provider的后代组件 通过Consumer获取

  2 Consumer
    后台组件用于 获取Provider提供的value的
*/
const { Provider, Consumer } = context;

export {
  Provider,
  Consumer
}
  • Provider 包裹app 并通过value 提供数据
js 复制代码
const data = {
  a: 10,
  b: 20
}

root.render(
  <Provider value={data}>
    <App />
  </Provider> 
);
  • 需要获取 数据的 后台组件引入 Consumer 获取数据(只针对class组件)
js 复制代码
class A extends Component {
  render() {
    return (
      <Consumer>
        {
          (data) => (
            <div>
              <h2>a组件</h2>
              {data.a}
            </div>
          ) 
        }
       
      </Consumer>
    )
  }
}

高阶组件 HOC (hign order component)

面试题?

fn(5)(6) // 30

js 复制代码
function fn(a){
  return function(b){
    return a*b
  }
}

高阶组件 本质上就是 高阶函数(特殊高阶),用来抽象或者 修饰 普通组件,可以给普通组件 添加额外视图,或者额外props等

实现:

特殊高阶函数, 接收参数就是被修饰的组件, 返回了一个组件

注意:

withXxx

问题?

高阶组件 本质上劫持普通组件 到内部使用, 返回新的组件, 造成 普通组件的props丢失

需要在 高阶组件内部 再一次 传递 props给被修饰的组件

js 复制代码
import React, { Fragment } from 'react';

const withTpl = (DecoratorComponent) => {
  return (props) => {
    console.log(props, '222');
    return (
      <Fragment>
        <h1>这是头部</h1>
        <DecoratorComponent {...props} msg="这是高阶组件送的props"/>
        <h1>这是尾部</h1>
      </Fragment>
    )
  }
}

使用

js 复制代码
export default withTpl(被修饰的组件)

useRef

函数式组件中用于获取 dom 或者 class子组件实例

js 复制代码
function Todo() {
  // 创建容器
  const btnRef = useRef();
  const childRef = useRef();
  const child2Ref = useRef();
  useEffect(() => {
    console.log(btnRef.current);
    console.log(childRef.current);
  }, [])
  return (
    <div>
      <button ref={btnRef}>按钮</button>
      <CommonHead ref={childRef}/>
    </div>
  )
}

问题?

函数式 组件 无法 通过ref 绑定到容器上

  • 函数式子组件 可以通过 forwardRef 将 子组件标签 绑定ref 容器 传递到子组件内部
jsx 复制代码
 <CommonHead2 ref={child2Ref}/>
 // 这是函数式子组件, 绑定容器可以通过 forwardRef 传递到子组件内部
  • 子组件内部 forwardRef 获取 容器 将内部 dom 挂载到容器上
js 复制代码
forwardRef(function CommonHead2(props,child2Ref) {
  return (
    <div>
      <h2 ref={child2Ref}>这是子组件2</h2>
    </div>
  )
})

useMemo

将一个函数计算返回值 缓存起来,并返回, 指定依赖, 在 函数式组件多次 更新,依赖没有改变,使用缓存的值

功能类似于vue的计算属性

js 复制代码
const sum = useMemo(() => {
  return num1+num2
}, [num1, num2])

注意:

1 useMemo回调一定要有返回值,

2 useMemo手动指定依赖, 在函数式组件重新调用时,依赖改变 才重新计算

useCallback

是useMemo语法糖

useMemo不同点在于:

1 useMemo 回调一定要有返回值, useCallback不需要

2 useMemo调用后返回的 是 回调 return 的值 useCallback直接返回 callback函数

3 都可以指定依赖, 依赖改变,useCallback 重新调用,重新返回新的callback,否则不调用使用上一次返回的callback函数

js 复制代码
 function Todo() {
  const [num2, setNum2] = useState(20);
  const [num3, setNum3] = useState(30);
  // 当Todo刷新重新调用,只有num2改变 addNum2才会得到一个新的函数, 使用上一次缓存的函数
  const addNum2 = useCallback(() => {
    console.log(1);
    setNum2(num2+1)
  }, [num2])

  return (
    <div>
      <button
        onClick={addNum2}
      >
        num2+
      </button>
      {num2}
      <br />

      <hr />
      <button
        onClick={() => {
          setNum3(num3 + 1);
        }}
      >
        num3+
      </button>
      {num3}
    </div>
  );
}

useContext

函数式组件中 获取 context对象 Provider提供的value

js 复制代码
const data = useContext(context); // 返回该 context Provider提供的value

react-router

特点:

万物皆组件

路由实现也是通过组件定义路由

提供了三个包

react-router 核心包 (包含了 react-router-dom和react-router-native)

react-router-dom 专门用于 b/s应用

react-router-native 专门用于 c/s 移动端app

基础使用

  • 安装
js 复制代码
npm i react-router-dom -S

路由根组件

包裹 App组件 路由才可以生效, 不同 根组件决定路由不同模式

HashRouter hash模式路由

BrowserRouter history模式路由

js 复制代码
import { HashRouter } from 'react-router-dom'

<HashRouter>
  <App/>
</HashRouter>

import { BrowserRouter } from 'react-router-dom'

<BrowserRouter>
  <App/>
</BrowserRouter>

定义路由组件

注意:

路由定义组件 即是路由定义 也是路由出口

js 复制代码
import { Routes, Route } from 'react-router-dom'

  <Routes>
      <Route path="/" element={<Home/>}/>
      <Route path="/about" element={<About/>}/>
      <Route path="/news" element={<News/>}/>
    </Routes>

导航组件

  • Link

    属性如下

    to 控制路由跳转path 可以是 字符串(直接写路由地址) 对象 {pathname: '/xxx'}

    replace boolean 跳转时 覆盖当前历史记录

    state 对象 (传参)

    注意:

    Link只是单纯导航,对于匹配路由 没有做高亮样式的处理

  • NavLink

    具有Link所有的属性,同时 增加了 匹配导航 高亮样式处理

    • 默认对于匹配路由添加 active类
    • 函数 自定义 className
    js 复制代码
      <NavLink to="/news" className={({isActive}) => isActive?'active':'inactive'}>新闻页</NavLink>
    • 内联自定义高亮样式
    js 复制代码
       <NavLink to="/news" style={
          ({isActive}) => isActive? {color:'red'}: {color: 'gray'}
       }>新闻页</NavLink>
    • 嵌套children
    js 复制代码
      <NavLink to="/about" replace>
          {
            ({isActive}) => (
              <button className={isActive?'aaa':'bbb'}>关于我们</button>
            )
          }
        </NavLink>

重定向组件

Navigate

注意:

Navigate 一定要有条件的渲染, 否则会造成路由死循环

to控制重定向的 地址

replace 默认为true

js 复制代码
 <Route path="/" element={<Navigate to="/home"/>}/>

利用表达式

js 复制代码
  {
    !isLogin()
    &&
    <Navigate to="/login"/>
  }

404问题

react-router 提供了特殊的path * 匹配任意路由地址 且优先级别最低

js 复制代码
 <Route path="*" element={<NotFound/>}/>

嵌套路由

  • 父级路由 Route 嵌套 子级路由规则
    子路由 path 可以省略 父path和 / 会自动补全
js 复制代码
        <Route path="/news" element={<News />}>
          <Route path="/news/native" element={<NativeNews />} />
          <Route path="abroad" element={<AbroadNews />} />
        </Route>
  • 父级路由组件中定义 Outlet组件作为出口
js 复制代码
function News() {
  return (
    <div>
      <h3>这是新闻页</h3>
      <Link to="/news/native">国内新闻</Link>
      <Link to="abroad">国外新闻</Link>
      <Outlet/>
    </div>
  )
}

编程式导航

  • useLocation
    每一次调用 返回新的路由静态参数信息 (类比 vue中的 useRoute)
js 复制代码
const location = useLoaction()

结合useEffect路由监听

js 复制代码
  useEffect(() => {
    console.log(location, 222);
  }, [location])

注意:

默认情况下 App.jsx 根组件 在路由切换时 不会重新触发了

想要重新触发,监听 location即可

原因:

useLocation 每一次需要返回新的对象,必须重新执行 useLocation,让每一次路由切换时 App.jsx重新调用 再一次调用useLocation 返回新的location

  • useNavigate
    得到编程式导航的api
    路由跳转
    历史记录操作等
js 复制代码
const navigate = useNavigate();
// 普通跳转
navigate('/home')
// replace跳转
navigate('/home', {
  replace: true
})
// 传递state 参数
navigate('/home', {
  state: {
    a: 10,
    b: 20
  }
})

路由跳转传参

  • 动态路由传参

    • 定义动态参数
    js 复制代码
       <Route path="/news/:id" element={<News />} />
    • 跳转时 按照 path 顺序 给 动态参数赋值
    js 复制代码
       navigate('/news/5')
    • 获取 使用 useParams hook函数
    js 复制代码
      const params = useParams();// 解析好的 动态参数
      // params  {id: 5}  
  • state传参

    • 传参
    js 复制代码
        <Link to="/news" state={{a: 10,b: 20}}>到新闻</Link>
        navigate('/news', {state: {a: 10,b: 20}})
    • 获取 使用 useLocation hook获取
    js 复制代码
      const location = useLoaction();
      // location.state.参数名

    注意:

    隐式 传参

    刷新不丢失

  • search传参

    • path后面携带 search参数
    js 复制代码
      navigate('/news?a=10&b=20')
    • useSearchParams 获取参数
    js 复制代码
      const [searchParams, setSearchParams] = useSearchParams();
      /* 
        searchParams 对象  获取 search获取
          searchParams.get('a') // 10  searchParams.get('b') // 20
        setSearchParams 函数 动态设置search参数
          setSearchParams('c=1000') 动态设置当前 url search参数的值
    */

路由登录鉴权

  • 直接在组件中判断
js 复制代码
    {
        !isLogin()
        &&
        <Navigate to="/login"/>
      }

    
    const navigate= useNavigate();
    useEffect(() => {
      if (!isLogin()) {
        navigate('/login')
      }
    }, [])
  • 在路由中判断
js 复制代码
      <Route path="/about" element={
          isLogin()? <About/> : <Navigate to="/login" />
        } />

改变 组件式路由为 config路由

路由懒加载

使用 React.lazy结合 Suspense 组件 实现

引入组件使用 lazy方法

js 复制代码
import { lazy } from "react";
const About = lazy(() => import("../pages/About"))

使用 Suspense 组件 包裹 异步引入的组件

js 复制代码
 <Suspense fallback={
      <div>
        加载中...
      </div>
    }>
    渲染异步的组件
  </Suspense>

React.memo 解决函数式组件 性能问题

高阶组件 解决 react 函数式组件 性能问题?

react 祖先组件更新 后代组件一定会更新,不管 导致祖先组件更新数据 有无在后代组件中使用

class组件可以通过 shouldComponentUpdate和 PureComponent解决?

函数式组件怎么解决?

函数式组件 更新 会让函数重新调用

React.memo 这是高阶组件,功能 在祖先组件 刷新重新调用 后代组件 只有当他的props中任意一个改变 后代组件才会重新触发 刷新视图

js 复制代码
import React, {memo} from 'react'
const Todo = (props) => {
  return (
    <div></div>
  )
}

export default memo(Todo)

redux

用的更多 基于 redux封装的库 比如 @reduxjs/toolkit dva mobx

js状态管理的库,

核心概念:

  • state 必须是只读的
  • 单一数据源
  • reducer 是纯函数
    函数的返回值 取决于 参数,且中间没有任何副作用

安装

js 复制代码
npm i redux -S

仓库创建

js 复制代码
import { legacy_createStore as createStore } from 'redux';
import { cloneDeep } from 'lodash'
const defaultState = {
  num: 10
}

/* 
  reducer必须是纯函数
  接收两个参数
    state修改前 store中的state
    参数2 组件 dispatch action
*/
const reducer = (state = defaultState, action ) => {
  // state是只读的 reducer 必须返回 新的state 仓库才能存储和刷新  必须深拷贝
  const newState = cloneDeep(state);


  return newState;
}
// 参数1 传入  仓库reducer
const store = createStore(reducer);

export default store

组件中使用 store

  • 获取state
js 复制代码
store.getState() // 获取仓库中的state
// 一般建议 将 获取的state 结合 useState存储, 当数据发生改变, 使用useState提供set函数 重新 store.getState() 重新赋值 让视图刷新

const [state, setState] = useState(store.getState());
  • 提交 action 修改state
js 复制代码
    // 触发一个行为,告诉仓库我们要做什么
    /* 
      action是一个对象
        必须有一个属性
          type 做什么
    */
  store.dispatch({
      type: 'ADD_NUM',
      data: 5
    })
  • reducer 判断 action的type 属性 对于state做修改
js 复制代码
const reducer = (state = defaultState, action ) => {
  // state是只读的 reducer 必须返回 新的state 仓库才能存储和刷新  必须深拷贝
  const newState = cloneDeep(state);
  // 判断action type 修改state
  switch (action.type) {
    case 'ADD_NUM':
      newState.num += action.data
      break;
  
    default:
      break;
  }
  return newState;
}
  • 组件中 通过 store.subscribe方法 订阅 state 变化 获取的store最新的值
js 复制代码
// 订阅仓库 state 改变
  store.subscribe(() => {
    setState(store.getState())
  })

单独提取actionCreator 和 actionType

  • action的type 是直接写的字符串 (不管在dispatch action 还是 在reducer中的判断)
    当 字符串 某个字符写错了 会造成数据不改变 且视图不做任何刷新 (无法调试代码)
    使用常量 保存type 字符串
  • dispatch action时, 直接写的对象, action 没有维护性 和 复用性

redux 处理异步

redux 通过 异步 redux 插件来完成

  • redux-thunk
  • redux-saga (基于es6 generator)

以redux-thunk 举例

1 安装

js 复制代码
  npm i redux-thunk -S

2 store 使用插件

js 复制代码
  import { legacy_createStore as createStore, applyMiddleware } from 'redux';
  import thunk from 'redux-thunk'
  import reducer from './reducer';
  // 参数1 传入  仓库reducer
  const store = createStore(reducer, applyMiddleware(reducer));

3 如何定义异步请求

改写 actionCreator

变成 函数 返回 函数

js 复制代码
  const fetch_items = (params = {}) => {
    return dispatch => {
      axios.get('xxx', {params}).then(res => {
        if (res.data.code === 200) {
          dispatch({
            type: 'xxx',
            data: res.data.data
          })
        }
      })
    }
  }

4 组件中 dispatch 异步 actionCreator

js 复制代码
  store.dispatch(fetch_items(page: 1, pageSize: 10))

lodash

react-redux 连接redux仓库和react组件

  • 安装
js 复制代码
npm i react-redux -S
  • 入口函数中 引入 Provider 和store Provider 将store 挂载
js 复制代码
import { Provider } from 'react-redux';
import store from './store';
      <Provider store={store}>
            <App />
        </Provider>
  • 组件中引入 useSelector 和 useDispatch 获取state 和提交action
js 复制代码
import { useSelector, useDispatch } from 'react-redux'

  const num = useSelector(state => state.num);
  const cates = useSelector(state => state.cates);

  dispatch(fetch_cates())
   dispatch(add_num(10))
scribe(() => {
    setState(store.getState())
  })

单独提取actionCreator 和 actionType

  • action的type 是直接写的字符串 (不管在dispatch action 还是 在reducer中的判断)
    当 字符串 某个字符写错了 会造成数据不改变 且视图不做任何刷新 (无法调试代码)
    使用常量 保存type 字符串
  • dispatch action时, 直接写的对象, action 没有维护性 和 复用性

redux 处理异步

redux 通过 异步 redux 插件来完成

  • redux-thunk
  • redux-saga (基于es6 generator)

以redux-thunk 举例

1 安装

js 复制代码
  npm i redux-thunk -S

2 store 使用插件

js 复制代码
  import { legacy_createStore as createStore, applyMiddleware } from 'redux';
  import thunk from 'redux-thunk'
  import reducer from './reducer';
  // 参数1 传入  仓库reducer
  const store = createStore(reducer, applyMiddleware(reducer));

3 如何定义异步请求

改写 actionCreator

变成 函数 返回 函数

js 复制代码
  const fetch_items = (params = {}) => {
    return dispatch => {
      axios.get('xxx', {params}).then(res => {
        if (res.data.code === 200) {
          dispatch({
            type: 'xxx',
            data: res.data.data
          })
        }
      })
    }
  }

4 组件中 dispatch 异步 actionCreator

js 复制代码
  store.dispatch(fetch_items(page: 1, pageSize: 10))

lodash

react-redux 连接redux仓库和react组件

  • 安装
js 复制代码
npm i react-redux -S
  • 入口函数中 引入 Provider 和store Provider 将store 挂载
js 复制代码
import { Provider } from 'react-redux';
import store from './store';
      <Provider store={store}>
            <App />
        </Provider>
  • 组件中引入 useSelector 和 useDispatch 获取state 和提交action
js 复制代码
import { useSelector, useDispatch } from 'react-redux'

  const num = useSelector(state => state.num);
  const cates = useSelector(state => state.cates);

  dispatch(fetch_cates())
   dispatch(add_num(10))
相关推荐
WeiShuai11 分钟前
vue-cli3使用DllPlugin优化webpack打包性能
前端·javascript
ice___Cpu17 分钟前
Linux 基本使用和 web 程序部署 ( 8000 字 Linux 入门 )
linux·运维·前端
JYbill20 分钟前
nestjs使用ESM模块化
前端
HinsCoder36 分钟前
【渗透测试】——Upload靶场实战(1-5关)
笔记·学习·安全·web安全·渗透测试·测试·upload靶场
加油吧x青年38 分钟前
Web端开启直播技术方案分享
前端·webrtc·直播
听风若依39 分钟前
排序学习笔记
笔记·学习
Filotimo_43 分钟前
【自然语言处理】实验三:新冠病毒的FAQ问答系统
人工智能·经验分享·笔记·python·学习·自然语言处理·pycharm
吕彬-前端1 小时前
使用vite+react+ts+Ant Design开发后台管理项目(二)
前端·react.js·前端框架
小白小白从不日白1 小时前
react hooks--useCallback
前端·react.js·前端框架
恩婧2 小时前
React项目中使用发布订阅模式
前端·react.js·前端框架·发布订阅模式