当前位置: 首页 > article >正文

【React 基础及高级用法】

【React 基础及高级用法】

  • React 基础
    • React 浅析
      • 传统的方式下,前端是如何构建用户界面的?
      • 按照这种逻辑,可以写一个 createElement
      • 回头看一下 react
      • React 到底是什么?
      • 前端在干什么?
      • React 的灵活性
    • 创建 react 工程
    • React 的基础能力
      • 子父组件
    • State 和 Props
        • 类组件
        • 函数组件
        • 子父组件传值的 props
    • 条件与列表
  • React 高级
    • 一些 use API
        • useState
        • useEffect
        • useLayoutEffect
        • useInsertionEffect
        • useEffect 如何模拟生命周期
    • Ref
        • Ref 的创建
        • 类组件 - createRef
        • 函数式组件 - useRef
        • Ref 的常见使用方式
    • Context
        • 类组件—context
        • 函数组件—useContext
    • Hoc
    • 优化相关 - useCallBack, useMemo, React.Memo
    • React 的更新逻辑

React 基础

React 浅析

传统的方式下,前端是如何构建用户界面的?

在这里插入图片描述
• JS:叫做 浏览器脚本
○ 本质上我就是在前端的 html 页面上去操作 DOM 的。
○ JS 是一种可以操作 DOM,对 DOM 进行增删改查的语言。

按照这种逻辑,可以写一个 createElement


<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <!-- 有一段 dom -->

    <div id="root">
        <div class="title">你好</div>
        <button>click me</button>
    </div>

    <!-- JavaScript 可以 操作 DOM,所以对于我这段 html, 我是不是可以只用 JavaScript 来实现 -->
    
</body>
<style>
    .title {
        font-size: 24px;
        font-weight: 700;
    }
</style>
<script>
    const div = document.createElement('div');
    const body = document.getElementsByTagName('body')[0];

    div.innerText = '你好';
    div.setAttribute('id', 'app');
    div.setAttribute('class', 'title')

    body.appendChild(div);
    document.getElementById('app').addEventListener('click', () => {
        console.log('hello luyi')
    });

    /**
        这样的话,我们尝试实现一个函数
        假如 我要书写一段这样的 HTML (DOM tree)
        <div id="root">
            <div class="title">你好</div>
            <button>click me</button>
        </div>

        // 理论上,我就可以用一段 createElement 的嵌套来表达。
        createElement('div', { id: 'root' }, createElement('div', {
            'class': 'title',
            'textContext': '你好'
        }), createElement('button', {
            'style': 'background-color: blue',
            'textContext': 'click me',
            'onclick': '() => {}'
        }))

    */

    function createElement(type, params, ...children) {
        // 1. 构建元素
        let ele;
        if(type.toLowerCase() === 'div') {
            ele = document.createElement('div')
        };
        if(type.toLowerCase() === 'button') {
            ele = document.createElement('button')
        };
        if(type.toLowerCase() === 'script') {
            // ....
        };

        // 2. 设置属性
        for(let key in params) {
            if(key === 'textContent') {
                ele[key] = params[key];
            } else if( ['onClick', 'onMouseDown'].includes(key) ) {
                // ... 事件的处理
                const method = key.toLowerCase().split('on')[1];
                ele.addEventListener(method, params[key]);
            } else {
                ele.setAttribute(key, params[key])
            }
        };

        // 3. 处理递归逻辑
        children.forEach(child => ele.appendChild(child));

        return ele;
    };

    const divRoot = createElement('div', {
        id: 'root'
    }, createElement('div', {
        class: 'title',
        textContent: '你好'
    }), createElement('button', {
        style: 'background-color: blue',
        textContent: 'click me',
        onClick: () => { console.log('hello luyi') }
    }));

    body.appendChild(divRoot)

    // *******  react babel 编译结果

    function App() {
        return React.createElement("div", {
          id: "root"
        }, React.createElement("div", {
          "class": "title"
        }, "\u4F60\u597D"), React.createElement("button", null, "click me"));
      }

</script>
</html>

在这里插入图片描述

回头看一下 react

• 为什么 JSX 可以返回 HTML?
• 为什么说 React 是一个运行时框架?和 vue 对比 , vue 是一个编译型框架?
○ 编译是我去转化代码,AST,编译完了,我才能丢到引擎(V8)里去执行。

tips:
我们学习框架,一定要立体,要从多个维度去认识。
深度,不止是源码,还有发展历程和设计思想。

Babel ->
○ @babel/preset-react
○ @babel/preset-typescipt
○ @babel/preset-env

React 到底是什么?

function App() {
    return <div>
         <div className="title">你好</div>
         <button>click me</button>
         <Submodule name={'xxx'} />
    </div>
};

function Submodule ({ name }) {
    return <div>submodule--{name}</div>
}


在 函数的情况下
- 支持使用 hooks 来表达,函数组件的状态和更新方式;
- jsx 足够的灵活,能够提供更好的动态化能力;

class Submodule {
    render() {
        return <div>submodule</div>
    }
}

在 类组件的情况下
- 支持生命周期,和状态表达,让我们更新界面。

前端在干什么?

在这里插入图片描述

React 的灵活性

template>
    <div>
        <div v-for="item of list" key="item">{{ item.name }}</div>
    </div>
</template>

<div>
    {[...list]
    .map(item => ({...item}))
    .map(item => <div>{item.name}</div>)}
</div>

<div>
    {(() => {
    
        for(item of list) {
        }
        
        return results
    
    })()}
</div>

// React 是不知道当前的改变,到底会有什么副作用的...

this.setState -> callback 
useEffect
unbatchedUpdate

创建 react 工程

npx create-react-app demo-app
npx create-react-app demo-app-ts --template typescript

React 的基础能力

子父组件

在这里插入图片描述
• 从 UI 的视角看
○ Class 组件渲染的是 render 函数中返回的内容;
○ Function 组件渲染的是,函数本身返回的内容;

State 和 Props

在 react 中,有一个概念叫做 state
• 如果我有一个数据,并且这个数据改变时,我需要触发界面更新。
○ 我要把这个数据定义成 state
○ 我要使用特殊的方法,去更新这个 state

类组件

setState 的方法。
类组件上,要用 setState
• State 的值,互相不影响
• setState 第二个参数是 一个 callback, 能拿到最新的 state 的值

import React, { Component } from 'react'

const SubModule = () => <div>submodule</div>

export default class ClassCom extends Component {

  constructor(props) {
    super(props);

    this.state = {
      number: 0,
      msg: 'hello luyi'
    }

    this.title = '类组件: state 的用法'
  }

  handleClick = (type) => {
    this.setState({
      number: this.state.number + (type === 'plus' ? 1 : -1)
    })
  };

  handleChange = (e) => {
    this.setState({
      msg: e.target.value
    })
  }

  handleAddFn = () => {
    this.setState({
      number: this.state.number + 1
    })
  }

  handleMinusFn = function() {
    this.setState({
      number: this.state.number - 1
    })
  }

  // 生命周期
  render() {

    const { number, msg } = this.state;

    return (
      <div>
        <h2>{this.title}</h2>
        <div>this is the number: {number}</div>
        {/* bind, call, apply */}
        <button onClick={this.handleClick.bind(this, 'plus')}>+</button>
        <button onClick={() => this.handleClick('minus')}>-</button>

        {/* this 绑定的问题 */}
        <button onClick={this.handleAddFn}>+</button>
        <button onClick={this.handleMinusFn.bind(this)}>-</button>
        
        {/* 受控组件的问题 */}
        <input value={msg} onChange={this.handleChange} />
        <input />
        <hr/>
        <SubModule />
      </div>
    )
  }
}

函数组件

useState

[state, dispatch] = useState(initState);

• State 作为组件的状态,提供给UI 渲染视图
• Dispatch, 用户修改state 的方法,同时触发更新
○ Dispatch 的参数可以是函数,可以不是,如果是函数,就更新为函数执行的结果,如果不是,直接更新为值;
○ initState :初始值
可以是函数,可以不是,如果是函数,就更新为函数执行的结果,如果不是,直接是值;

import React, { useState } from 'react'

export default function FunCom( {name }) {

    const title = '函数组件: state 的用法';

    const [number, setNumber ] = useState(0);
    const [msg, setMsg] = useState('hello ');

    const handleClick = (type) => {
        setNumber(number + (type === "plus"?1:-1))
    }

    const handleChange = (e) => {
        setMsg(e.target.value)
    }

  return (
    <div>
       <h2>{title}</h2> 
       <div>{number}</div>
       <input value={msg} onChange={handleChange} />
       <button onClick={() => handleClick('plus')}>+</button>
       <button onClick={handleClick.bind(null, 'minus')}>-</button>
    </div>
  )
}

子父组件传值的 props

传值: 父到子
传函数: 子到父

条件与列表

import React, { useState } from 'react'

const Hide = () => <div>to title</div>

const Title = ({title}) => title.length ? <h3>{title}</h3> : <Hide />

export default function Other() {

    const list = ['luyi', 'yunyin', 'xianzao'];
    const [show, setShow] = useState(false);

  return (
    <div>

        <h2>条件与列表</h2>
        <Title title={show? "数据管理": ''} />
        <button onClick={() => setShow(!show)}>{show?"setHide":"setShow"}</button>
        <ul>
            {list.map(item => <li key={item}>{item}</li>)}
        </ul>

        <ol>
            {
                (() => {
                    let res = [];
                    for(let item of list) {
                        res.push(<li key={item}>{item}</li>)
                    }
                    return res
                })()
            }
        </ol>
    </div>
  )
}

React 高级

在这里插入图片描述

一些 use API

useState
useEffect

useEffect(() => destory, deps)

  • Callback: () => destory,是第一个参数,是一个 callback 函数
    ○ Destory:作为这个 callback 的返回值,会在callback 执行之前调用,用于清除上一次 callback 的副作用;
    ○ deps:第二个参数,是一个数组,数组中的值发生变化的话,会执行这个 callback 返回的 destory,然后再执行这个 callback 函数。
useLayoutEffect
  • 同步执行
  • useLayoutEffect 是在 DOM 更新之后,浏览器绘制之前执行的,可以方便的修改DOM
  • 如何选择:如果修改DOM,就用 useLayoutEffect,否则就用 useEffect
useInsertionEffect

是在 DOM 更新之前执行的,那么 对于 css-in-js 的场景,可以解决性能问题。

import React, { useEffect, useInsertionEffect, useState } from 'react'

const getBookList = () => new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(['React 开发实战', 'Vue 原理解析', 'Webpack 从入门到精通']);
    }, 1000)
});

const useBookList = () => {
    const [list, setList] = useState([]);

    useAsyncEffect(async () => {
        const _list = await getBookList();
        setList(_list);
    })

    return [list, setList];
};

const useAsyncEffect = (cb) => {
    async function fetchData() {
        await cb();
    }
    fetchData();
}

export default function Effect() {

    const [num, setNum] = useState(0);
    const [list, setList] = useState([]);

    useEffect(() => {
        setNum(list.length);
    },[list]);

    // 相当于是 mounted, componentDidMount 生命周期
    useEffect(() => {
        getBookList().then(res => {
            setList(res);
        })
    },[])

    useInsertionEffect(() => {
        const style = document.createElement("style");
        style.innerHTML = `
            .xxx {
                color: blue
            }
        `;
        document.head.appendChild(style);
    })

  return (
    <div>
        <h3>the length is {num}</h3>
        <ul>
            {list.map((item) => <li key={item}>{item}</li>)}
        </ul>
        <button onClick={() => setList(['1','2','3'])}>setList</button>
    </div>
  )
}

useEffect 如何模拟生命周期
import React, { useEffect, useState } from 'react'

export default function LifeCycleMock(props) {

  const [state, dispatch] = useState(() => {
    console.log('getDerivedStateFromProps')
    return ''
  });

  useEffect(() => {
    console.log('componentDidMount');
    // 数据请求
    new Promise((resolve) => {
        setTimeout(() => {
            resolve('luyi')
        }, 300)
    }).then(res => {
        dispatch(res);
    });

    return () => {
        // 做一些监听器的销毁
        console.log('componentWillUnmount')
    };
    // deps 没有任何依赖,也就意味着,我只在初始化的时候,执行一次,destory 函数,只在销毁的时候执行一次
  }, []);

  useEffect(() => {
    console.log('componentDidUpdate')
  });

  useEffect(() => {
    console.log('componentWillReceiveProps');
    // 外面会传过来一个数据,同时我内部会想要自己可以处理
  }, [props]);

  return (
    <div>LifeCycleMock</div>
  )
}

Ref

Ref 的创建
类组件 - createRef

import React, { Component, createRef } from 'react';

export default class ClassRef extends Component {

  constructor(props) {
    super(props);

    // 用来拿到某一个元素,组件的“句柄”/“实例”
    this.eleRef = createRef();
    this.inputRef = createRef();
  }

  handleFocus = () => {
    this.inputRef.current.focus()
  }

  handleClick = () => {
    console.log(this.eleRef.current)
  }

  render() {
    return (
      <div> 
      <h3>Class Ref</h3>
      <div id='usingRef' ref={this.eleRef} >eleRef</div>
      <input ref={this.inputRef} />
      <button onClick={this.handleFocus}>focus</button>
      <button onClick={this.handleClick}>click</button>
      </div>
    );
  }
}

函数式组件 - useRef

import React, { useRef } from 'react'

export default function FuncRef() {

  const inputRef = useRef(null);
  const eleRef = useRef(null);

  const handleClick = () => {
    inputRef.current.focus()
  }

  return (
    <div>
        <h3>function Ref</h3>
        <input ref={inputRef} />
        <button onClick={handleClick}>focus</button>
    </div>
  )
}

Ref 的常见使用方式
  • 解决闭包陷阱问题;
  • 组件封装;
    在这里插入图片描述
    Visible 是谁的属性
    • 是弹窗的属性
    • 提供一种能力,让外面可以调用我的方法。
    • forwardRef, 本质是处理子父组件之间的 ref 传递。

import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react'

export default function VisibleApi() {

    const modalRef = useRef(null);

    const inputRef = useRef(null);

  return (
    <div>
        <button onClick={() => modalRef.current.setVisible(true)}  >显示</button>
        <button onClick={() => inputRef.current.focus()}  >focus</button>
        <FancyModal ref={modalRef} />
        <FancyInput ref={inputRef} />
    </div>
  )
}

const Modal = (props, ref) => {

    const [vis, setVis] = useState(true);
    const setVisible = (val) => setVis(val);
    const getData = () => "hello"

    useImperativeHandle(ref, () => ({
        setVisible,getData
    }))
    return <div
        style={{ display: vis?'block':'none', 
                position:'relative', 
                height: '200px',
                background: '#ffeedd' }}
    >
        <button style={{ position: 'absolute', right: '10px', top: '10px'}} onClick={() => setVis(false)}>close</button>
        <div>我是一个模拟弹窗</div>
    </div>
}

const Input = (props, ref) => {
    return <input ref={ref} />
}

const FancyModal = forwardRef(Modal);

const FancyInput = forwardRef(Input);

Context

类组件—context
函数组件—useContext

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

// 组件使用一个 withRouter 的函数包裹了以后,props 上面就能拿到 history 对象。
// 就是把 history 通过 context 传递了下去,并且使用 context 封装了一个高阶组件。

const NavContext = createContext(null);
const history = window.history;

export default function FuncContext() {
  return (
    <NavContext.Provider value={history}>
        <Parent />
    </NavContext.Provider>
  )
};

const Parent = () =>  <><Child1 /><WithRouterChild2 name={'luyi'} /></>

const Child1 = (props) => {
    // 我通过 useContext, 是能拿到 最外层传入的 context 的值的。
    const his = useContext(NavContext);
    return <button onClick={() => his.pushState({}, undefined, 'hello')}>nav to hello</button>
}

const Child2 = ({ name, his}) => {
    return <button onClick={() => his.pushState({}, undefined, 'hello')}>nav to hello -- {name}</button>
}

const withRouter = (Component) => {
    return (props) => {
        const his = useContext(NavContext);
        return <Component {...props} his={his}/>
    }
};
// WithRouterChild2 是一个组件。Child2 也是一个组件。那么:
// withRouter 这个函数,接收一个组件,并且返回一个组件。
// 高阶函数:参数可以是函数,返回值也可以是函数
// 高阶组件:参数可以是组件,返回值也可以是组件
const WithRouterChild2 = withRouter(Child2);

Hoc

• 属性代理 – withRouter
• 反向继承 – LogProps

优化相关 - useCallBack, useMemo, React.Memo

React 的更新逻辑


// App -> FunctionComponent 
// Fiber -> momezied -> Hook 链表 -> 为什么 useApi 不能有条件判断,

const Demo = {
    Foo: Submodule
}

function App() {
    
    useMemo
    useCallback
    
    return <div>
         <div className="title">你好</div>
         <button>click me</button>
         <Submodule name={'xxx'} />
    </div>
};

// React.Memo
function Submodule ({ name }) {
    // 一定会执行,但是不代表 dom 会更新
    console.log('一定会执行')
    return <div>submodule--{name}</div>
}


http://www.kler.cn/a/452884.html

相关文章:

  • kong网关使用pre-function插件,改写接口的返回数据
  • Cesium材质——Material
  • 湖南引力:低代码助力实现智慧养老管理系统
  • python中使用selenium执行组合快捷键ctrl+v不生效问题
  • 智驾感知「大破局」!新一轮混战开启
  • 极狐GitLab 17.7正式发布,可从 GitLab 丝滑迁移至极狐GitLab【二】
  • Docker安装Neo4j
  • SQL进阶技巧:如何求解最大矩形面积问题? | LeetCode 84- 柱状图中最大的矩形
  • 【GD32】从零开始学GD32单片机 | DAC数模转换器 + 三角波输出例程
  • 浅谈TARA在汽车网络安全中的关键角色
  • adb 安装教程
  • nginx-rewrite(多种实现方法)
  • 从一次线上故障聊聊接口自动化测试
  • QT创建一个模板槽和信号刷新UI
  • 《计算机网络(第7版)-谢希仁》期末考试复习题和答案(总结整理)
  • python file seek tell
  • 【2025最新计算机毕业设计】新型吃住玩一体化旅游管理系统【提供源码+答辩PPT+文档+项目部署】
  • 金仓数据库安装-Kingbase v9-centos
  • STM32串口无法正常中断
  • Spring Boot 中 WebClient 的实践详解
  • Go C编程 第6课 无人机 --- 计算旋转角
  • More Effective C++ 条款 1:仔细区别 pointer 和 reference
  • Leetcode经典题17--两数之和
  • 记我的Springboot2.6.4从集成swagger到springdoc的坎坷路~
  • Java 中的ArrayList常用方法总结
  • mysql mmm和mha对比