react入门笔记

react

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

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

create-react-app

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

npm i create-react-app -g
create-react-app 项目名 // 创建项目

jsx

xml in js

<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
  • 标签支持 批量 展开 一个对象 自动将对象每个属性转换成标签的属性
  const obj = {
    id: 'box',
    className: 'wrap'
  }
  <div {...obj}>1111</div>

react组件

注意
所有组件 首字母必须大写
文件 后缀名建议 为 jsx

函数式组件

现在是 趋势

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


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

react vscode 插件

ES7+ React/Redux/React-Native snippets

重要作业 复习 class

定义属性 两种方式
定义方法两种方式

继承
静态属性静态方法

类组件

  • 定义
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>
    )
  }
}
  • 使用组件
<App title="主标题" subTitle="副标题"/>
// 相当于 new 类  实例调用render 传递 自定义属性 挂载实例的 props属性上

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

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

jsx原理

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

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

react组件的样式

内联

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

引入 外部的 样式文件

  • 引入外部的css

  • 使用 css预处理器 sass
    cra 内置 sass 环境

    • 安装sass
        npm i sass -D
    

    注意:
    不管是 css还是外部 sass文件,都没有做任何 作用域限制,作用到全局所有的标签
    选择器编写不要 污染

css module

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

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

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

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

    scss 嵌套规则 编译成平行

    定义:global即可嵌套

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

    使用时

      <div className={styles.box2}>
          <div className="a">
            <div className="b">aaaa</div>
          </div>
        </div>
    

css in js

理念:万物皆组件
styled-components
原理:
通过样式组件 定义 样式和结构

  • 安装
npm i styled-components -S
  • 基础使用
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的类型校验

  • 安装
npm i prop-types -S
  • 使用
// 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 中定义默认值即可

class Todo extends Component {}
const Todo = (props) => {}
Todo.defaultProps = {}

props.children

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

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

class组件

state

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

state管理组件 内部状态
语法:
直接定义 实例的state属性,在属性管理数据即可

  • 给实例定义 state属性
class Todo extends Component {
  // 外部直接定义
  state = {
    msg: '这是实例自己的状态'
  };
  // 在constructor中定义
  constructor(){
    super();
    this.state = {
      msg: '这是实例自己的状态',
      isBeauty: true
    }
  };
}
  • 修改state
    注意:
    react 不是mvvm框架, 不能直接修改state, 对于state 父类原型 提供了 两个方法让视图刷新

    • 直接修改state 调用 forceUpdate 强烈不推荐
    • 使用setState 修改状态
      1 传对象
        this.setState({
          msg: '修改的值',
          isBeauty: !this.state.isBeauty
        })
      
      2 传函数,函数返回值中修改
        this.setState((state, props) => {
          return {
            msg: '这是修改后的值',
            isBeauty: !state.isBeauty
          }
        })
      

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

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

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

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

react事件绑定

react合成事件

on事件首字母大写
onClick onMouseover onMousedown

绑定合成事件语法

<button onClick={事件函数}>按钮</button>

注意:
事件函数不能加括号

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

  • 行内定义箭头函数
    不建议: 造成业务嵌套 视图模板中
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
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返回的函数
    较为推荐
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 指向实例

推荐写法

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
    })
  }
}

事件对象

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

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

事件传参

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

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条件渲染
    使用短路或者三目表达式 实现 条件渲染
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方法
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>
    )
  }
}
  • 渲染富文本
<div dangerouslySetInnerHTML={{__html: this.state.content}}></div>
  • 容器组件
    问题?
    jsx语法要求,任意一个jsx 必须 包裹在一个闭合标签, 往往会造成, dom树 层级 过深, react提供容器组件 Fragment 作为 jsx容器 优点, 渲染时不会渲染任何标签
import React, { Fragment } from 'react'
import Todo from './Todo'

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

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

class Todo extends Component {
  render() {
    return (
      <>
        <h2>这是todo</h2>
      </>
    )
  }
}

表单值得绑定

  • 绑定初始值 (表单控件 初始值 等于 某个 状态,值是可变,变化后不会 改变状态)
    提供了两个属性分别是
    defaultValue
    defaultChecked

      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即可

      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 传递给子组件这个方法,子组件 调用 该方法 通过方法参数 可以 传递 子组件数据

    • 父组件
      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 传递方法
      class Todo extends Component {
        state = {
          msg: '这是子组件的数据'
        }
        render() {
          
          return (
            <div>
              <button onClick={
                () => {
                  this.props.fn(this.state.msg)
                  {/*子组件调用 父组件传递的方法*/}
                }
              }>子向父通信</button>
              <h2>子组件</h2>
            </div>
          )
        }
      }
    
  • 兄弟组件通信
    可以利用PubSub 发布订阅的 js库进行兄弟组件通信

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

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

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

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 (开发不用、面试要用)
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

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

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

useEffect(() => {
  // 相当于 componentDidMount和 componentDidUpdate
})

注意: 这种不要用,经常会造成死循环
2 useEffect 定义第二个参数 是数组 数组中定义 更新阶段触发的 依赖
更新阶段只有依赖中 任意一个发生改变时 才触发
功能:
类似 vue中watch(立即触发的watch)
解决 useState set函数修改数据 异步的问题

useEffect(() => {}, [a,b,c])

3 模拟 初始化完成的 生命周期钩子函数
作用: 类似 componentDidMount vue 的 onMounted
组件初始化 请求函数的调用
第三方插件的初始化

// 定义空的依赖即可
useEffect(() => {}, [])

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

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

context

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

  • 创建context对象
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 提供数据
const data = {
  a: 10,
  b: 20
}

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

高阶组件 HOC (hign order component)

面试题?
fn(5)(6) // 30

function fn(a){
  return function(b){
    return a*b
  }
}

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

实现:
特殊高阶函数, 接收参数就是被修饰的组件, 返回了一个组件
注意:
withXxx

问题?
高阶组件 本质上劫持普通组件 到内部使用, 返回新的组件, 造成 普通组件的props丢失
需要在 高阶组件内部 再一次 传递 props给被修饰的组件

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>
    )
  }
}

使用

export default withTpl(被修饰的组件)

useRef

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

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 容器 传递到子组件内部
 <CommonHead2 ref={child2Ref}/>
 // 这是函数式子组件, 绑定容器可以通过 forwardRef 传递到子组件内部
  • 子组件内部 forwardRef 获取 容器 将内部 dom 挂载到容器上
forwardRef(function CommonHead2(props,child2Ref) {
  return (
    <div>
      <h2 ref={child2Ref}>这是子组件2</h2>
    </div>
  )
})

useMemo

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

功能类似于vue的计算属性

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函数

 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

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

基础使用

  • 安装
npm i react-router-dom -S

路由根组件

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

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

<HashRouter>
  <App/>
</HashRouter>

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

<BrowserRouter>
  <App/>
</BrowserRouter>

定义路由组件

注意:
路由定义组件 即是路由定义 也是路由出口

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
      <NavLink to="/news" className={({isActive}) => isActive?'active':'inactive'}>新闻页</NavLink>
    
    • 内联自定义高亮样式
       <NavLink to="/news" style={
          ({isActive}) => isActive? {color:'red'}: {color: 'gray'}
       }>新闻页</NavLink>
    
    • 嵌套children
      <NavLink to="/about" replace>
          {
            ({isActive}) => (
              <button className={isActive?'aaa':'bbb'}>关于我们</button>
            )
          }
        </NavLink>
    

重定向组件

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

to控制重定向的 地址
replace 默认为true

 <Route path="/" element={<Navigate to="/home"/>}/>

利用表达式

  {
    !isLogin()
    &&
    <Navigate to="/login"/>
  }

404问题

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

 <Route path="*" element={<NotFound/>}/>

嵌套路由

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

编程式导航

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

结合useEffect路由监听

  useEffect(() => {
    console.log(location, 222);
  }, [location])

注意:
默认情况下 App.jsx 根组件 在路由切换时 不会重新触发了
想要重新触发,监听 location即可
原因:
useLocation 每一次需要返回新的对象,必须重新执行 useLocation,让每一次路由切换时 App.jsx重新调用 再一次调用useLocation 返回新的location

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

路由跳转传参

  • 动态路由传参

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

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

    注意:
    隐式 传参
    刷新不丢失

  • search传参

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

路由登录鉴权

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

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

改变 组件式路由为 config路由

路由懒加载

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

引入组件使用 lazy方法

import { lazy } from "react";
const About = lazy(() => import("../pages/About"))

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

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

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

高阶组件 解决 react 函数式组件 性能问题?
react 祖先组件更新 后代组件一定会更新,不管 导致祖先组件更新数据 有无在后代组件中使用

class组件可以通过 shouldComponentUpdate和 PureComponent解决?
函数式组件怎么解决?
函数式组件 更新 会让函数重新调用

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

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

export default memo(Todo)

redux

用的更多 基于 redux封装的库 比如 @reduxjs/toolkit dva mobx
js状态管理的库,
核心概念:

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

安装

npm i redux -S

仓库创建

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
store.getState() // 获取仓库中的state
// 一般建议 将 获取的state 结合 useState存储, 当数据发生改变, 使用useState提供set函数 重新 store.getState() 重新赋值 让视图刷新

const [state, setState] = useState(store.getState());
  • 提交 action 修改state

    // 触发一个行为,告诉仓库我们要做什么
    /* 
      action是一个对象
        必须有一个属性
          type 做什么
    */
  store.dispatch({
      type: 'ADD_NUM',
      data: 5
    })
  • reducer 判断 action的type 属性 对于state做修改
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最新的值
// 订阅仓库 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 安装

  npm i redux-thunk -S

2 store 使用插件

  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
变成 函数 返回 函数

  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

  store.dispatch(fetch_items(page: 1, pageSize: 10))

lodash

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

  • 安装
npm i react-redux -S
  • 入口函数中 引入 Provider 和store Provider 将store 挂载
import { Provider } from 'react-redux';
import store from './store';
      <Provider store={store}>
            <App />
        </Provider>
  • 组件中引入 useSelector 和 useDispatch 获取state 和提交action
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 安装

  npm i redux-thunk -S

2 store 使用插件

  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
变成 函数 返回 函数

  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

  store.dispatch(fetch_items(page: 1, pageSize: 10))

lodash

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

  • 安装
npm i react-redux -S
  • 入口函数中 引入 Provider 和store Provider 将store 挂载
import { Provider } from 'react-redux';
import store from './store';
      <Provider store={store}>
            <App />
        </Provider>
  • 组件中引入 useSelector 和 useDispatch 获取state 和提交action
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))
  • 22
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

苦逼的猿宝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值