Git Product home page Git Product logo

react's Introduction

Top Langs

🛠 Tech Stack

JavaScript TypeScript

Node.js Vue React NestJs Vite Nuxt Git GitHub Docker

JavaScript工具:

  1. ESLint,代码格式
  2. Lodash.js,功能齐全并且很强大的JavaScript工具库,精简代码;
  3. TypeScript,静态类型

Git 提交规范:

  • feat 增加新功能
  • fix 修复问题/BUG
  • Optimize:需要填写对应需求来源
  • style 代码风格相关无影响运行结果的
  • perf 优化/性能提升
  • refactor 重构
  • revert 撤销修改
  • test 测试相关
  • docs 文档/注释
  • chore 依赖更新/脚手架配置修改等
  • workflow 工作流改进
  • ci 持续集成
  • types 类型定义文件更改
  • wip 开发中

Git 生僻的命令:

  • git update-index --assume-unchanged 文件路径,暂时忽略文件的变更
  • git reset --hard 目标版本号:将版本回退
  • git merge targetbranch:将targetbranch合并到当前分支

Linux命令:

  • scp /path/to/local/file username@server:/path/to/server/directory 终端命令上传文件至服务器

react's People

Contributors

dependabot[bot] avatar duxinyues avatar

Stargazers

 avatar  avatar  avatar  avatar

react's Issues

react组件性能优化React.memo和useMemo

之前有一篇记录了,如何设计合理的react组件

设计高性能react组件的思路

React.memo

本质上是高阶组件,对传入的组件进行封装,调用方法如下:
首先我们声明一个组件D,

function ComD(props) {
    console.log("D组件")
    return <div>{props.text}</div>;
}

export default ComD;

在父组件的引入如下:

import { useState } from "react"
import ChildA from "./ChildA";
import ChildB from "./ChildB";
import ChildC from "./ChildC";
import ComD from "./ChildD";
function Home() {
    const [textA, setTextA] = useState("A组件的文本");
    const [textB, setTextB] = useState("B组件的文本");
    const [textC, setTextC] = useState("C组件的文本");
    const [textD, setTextD] = useState("D组件的文本");
    const changeA = () => {
        setTextA("A组件的文本发生的改变");
    }
    const changeB = () => {
        setTextB("B组件文本发生改变了");
    }
    const changeC = () => {
        setTextC("C组件文本发生改变了");
    }
    const changeD = () => {
        setTextD("D组件的文本更新了");
    }
    return <div className="App">
        <button onClick={changeA}>changeA</button>
        <button onClick={changeB}>changeB</button>
        <button onClick={changeC}>changeC</button>
        <button onClick={changeD}>changeD</button>
        <ul>
            <li><ChildA text={textA} /></li>
            <li><ChildB text={textB} /></li>
            <li><ChildC text={textC} /></li>
            <li><ComD text={textD} /></li>
        </ul>
    </div>
}
export default Home 

和前面的操作一样,当我们点击按钮更新A组件的时候,整体父组件Home的props发生了改变,接着是A组件的文本发生改变。其他组件保持不变,这可以实现我们想要的效果。

但是在Home组件的props更新的时候,D组件也重新渲染了一次:
在这里插入图片描述
这就是D组件出现了不必要的渲染。也是我们在设计组件的时候需要考虑的问题。

React.memo的使用

在D组件原来基础上修改一下:

import React from "react";
function ComD(props) {
    console.log("D组件执行了")
    return <div>{props.text}</div>;
}
function areEqual(preveProps,nextProps) {
    if (nextProps.text === preveProps.text) {
        return true
    }
    return false;
}
export default React.memo(ComD,areEqual);

React.memo的第一个参数是组件,第二个组件就是shouldComponentUpdate的功能。当然第二个参数是可选的当没有传参的时候,React.memo就会为组件对props进行浅比较逻辑。

React.memo和shouldComponentUpdate不一样的地方是,React.memo只是对props进行判断,并没有判断组件的state。

效果如图:
在这里插入图片描述

useMemo

useMemo就是属于Hooks系列的了,

到这里,包括之前的那篇,所说的组件优化,都是针对整个组件而言。

那么有时候我们只需要一个部分的逻辑,而不是整个组件。这就是useMemo的场景了。

所以可以说useMemo控制的是是否需要重复执行一个逻辑片段。

再一次对组件D进行改造:

import React, { useMemo } from "react";
function ComD(props) {
    console.log("D组件执行了")
    const renderText = (text) => {
        console.log("执行了text文本");
        return <p>D组件的文本:{text}</p>
    }
    const renderCount = (count) => {
        console.log('执行了count数字')
        return <p>D组件的count:{count}</p>
    }
    const  textContent = useMemo(()=>renderText(props.text),[props.text]);
    const countContent = useMemo(()=>renderCount(props.countD),[props.countD])
    return <React.Fragment>
        {textContent}
        {countContent}
    </React.Fragment>;
}

export default ComD;

效果如下图:

在这里插入图片描述

[02]创建react应用的react元素和react组件

当我们创建好一个react应用之后,react应用创建的第一个组件会使用到react、React DOM和prop-types。

React DOM是react的渲染器,将组件渲染成为DOM或者是服务器端渲染的字符串。

prop-types的作用是对给组件传递的数据做一个类型检测。


调用React DOM的render方法来创建和管理组件,render方法需要两个参数,一个是应用需要渲染的react组件【ReactElement类型的元素】,另一个就是整个应用的容器【DOMElement类型的元素,也就是DOM元素】。


“React元素是React中轻量、无状态、不可变的基础类型。React元素有ReactComponent-Element和ReactDOMElement两种类型。ReactDOMElement是DOM元素的虚拟表示。ReactComponentElement引用了对应着React组件的一个函数或类。”

我们在使用create-react-app创建react应用的时候就已经自动创建了一个DOM元素了。如:

document.getElementById('root')

生成的index.js代码如下:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);
serviceWorker.unregister();

其中<App/>就是React元素或者说是react组件。




使用React.createElement()方法构建一个react元素

它接收字符串或者是组件【可以是扩展了React.Component的类组件,也可以是一个函数】、属性对象和子元素集合,最后返回一个React元素。

语法:
React.createElement(String/ReactClass type,[object props],[children...])


type:传入一个字符串或者是一个react类,表明我们要创建的元素;

props:传递一个属性对象,指明了我们在创建的这个元素上定义什么样的属性或者是组件类的实例属性。

children:表示我们所定义的元素中它包含了哪些其他元素,就像在写HTML代码中一个元素嵌套着另一个元素,同一个道理。

现在我们修改一下react应用的第一个组件【使用React.createElement()】,代码:

import React from "react";
import { render } from "react-dom";
const node = document.getElementById("element")

const root = React.createElement(
    "div",
    {},
    React.createElement(
        "h1",
        {},
        "Hello,world!",
        React.createElement(
            "a",
            { href: "http://www.qingzhuyue.cn" },
            React.createElement(
                "h1",
                {},
                "读心悦"
            ),
            React.createElement(
                "em",
                {},
                "...and  now it really is !"
            )
        )))
const CreateElement = render(root, node);


使用React.Component创建react组件,它是通过声明一个继承于React.Component抽象类的JavaScript类来实现创建组件,在这个组件里面必须有一个render方法,而且只能有一个这样的方式.render放回的是一个React值或者是一个react元素的数组,如下代码:

import React from "react";
class ClassComponent extends Reacrt.Component {

    render() {
        return <div>
            使用类创建的react组件
   </div>
    }
}


export default ClassComponent





react、echarts柱状图

import { useEffect } from "react";
import * as echarts from 'echarts';
function LoomEchart() {
    useEffect(() => {
        const day = ["第1天", "第2天", "第3天", "第4天", "第5天", "第6天", "第7天", "第8天", "第9天", "第10天", "第11天", "第12天", "第13天", "第14天", "第15天"], data = [7, 2, 54, 2, 5, 5, 12, 8, 32, 20, 11, 12, 43, 14, 70]
        var myChart = echarts.init(document.getElementById('loomEchart'));
        myChart.setOption({
            tooltip: {
                trigger: "axis",
                triggerOn: "mousemove",
                show: true,
                axisPointer: {
                    type: "shadow",
                },
                formatter(params) {
                    for (let x in params) {
                        return (
                            params[x].name +
                            ":" +
                            params[x].data +
                            "%"
                        );
                    }
                },
            },
            grid: {
                left: 0,
                right: 0,
                bottom: 30,
                top: 0
            },
            xAxis: {
                axisLabel: {
                    formatter: function () {
                        return "";
                    }
                },
                "axisLine": {       //y轴
                    "show": false
                },
                "axisTick": {       //y轴刻度线
                    "show": false
                },
                "splitLine": {     //网格线
                    "show": false
                },
                show: false,
                type: 'category',
                data: day,
            },
            yAxis: {
                show: false,
                axisLabel: {
                    formatter: function () {
                        return "";
                    }
                },
                "axisLine": {       //y轴
                    "show": false
                },
                "splitLine": {     //网格线
                    "show": false
                },
                "axisTick": {       //y轴刻度线
                    "show": false
                },

            },
            series: [
                {
                    type: 'bar',
                    barWidth: '10',
                    color: "#2390fa",
                    data: data
                }
            ]
        });
    }, [])
    return <div id="loomEchart" style={{ width: "400px", height: "100px" }}></div>
}

export default LoomEchart

react引入Echart【叠堆柱状图组件】

import { useEffect } from "react";
import * as echarts from 'echarts';
function Echarts(props) {
    useEffect(() => {
        let totalData = [];// 动态设置柱状图的颜色
        let colors = ["#2390fa", "#36cbcb", "#4ecb73", "#fbd437", "#975fe5"]; // 动态设置柱状图的颜色
        props.data.map((item, index) => {
            totalData.push({
                name: item.companyName,
                type: 'bar',
                stack: '库存',
                barWidth: 10,
                color: colors[index],
                data: [item.stockWeight]
            })
        })
        var myChart = echarts.init(document.getElementById('main'));
        myChart.setOption({
            tooltip: {
                trigger: 'axis',
                axisPointer: {            // 坐标轴指示器,坐标轴触发有效
                    type: ''        // 默认为直线,可选为:'line' | 'shadow'
                },
                backgroundColor: "rgba(255,255,255,.8)",
                position: function (point, params, dom, rect, size) {
                    // 鼠标坐标和提示框位置的参考坐标系是:以外层div的左上角那一点为原点,x轴向右,y轴向下
                    // 提示框位置
                    var x = 0; // x坐标位置
                    var y = 0; // y坐标位置

                    // 当前鼠标位置
                    var pointX = point[0];
                    var pointY = point[1];

                    // 提示框大小
                    var boxWidth = size.contentSize[0];
                    var boxHeight = size.contentSize[1];

                    // boxWidth > pointX 说明鼠标左边放不下提示框
                    if (boxWidth > pointX) {
                        x = 9;
                    } else { // 左边放的下
                        x = pointX - boxWidth;
                    }

                    // boxHeight > pointY 说明鼠标上边放不下提示框
                    if (boxHeight > pointY) {
                        y = 9;
                    } else { // 上边放得下
                        y = pointY - boxHeight;
                    }

                    return [x, y];
                }
            },
            grid: {
                left: 0,
                right: 0,
                bottom: 25,
            },
            xAxis: {
                axisLabel: {
                    formatter: function () {
                        return "";
                    }
                },
                "axisLine": {       //y轴
                    "show": false
                },
                "axisTick": {       //y轴刻度线
                    "show": false
                },
                "splitLine": {     //网格线
                    "show": false
                },
                show: false,
            },
            yAxis: {
                show: false,
                axisLabel: {
                    formatter: function () {
                        return "";
                    }
                },
                "axisLine": {       //y轴
                    "show": false
                },
                "splitLine": {     //网格线
                    "show": false
                },
                "axisTick": {       //y轴刻度线
                    "show": false
                },
                data: ['库存']
            },
            series: totalData
        });
    }, [])
    return <div id="main" style={{ width: "400px", height: "100px" }}>厕所嘎儿</div>
}

export default Echarts

React的useEffect与useLayoutEffect

在React中有一个特性和useEffect同等地位的,useLayoutEffect。

既然是相同的功能,却是两个不同的PAI。那先看看useEffect与useLayoutEffect分别在什么时候执行?先看看组件代码:

import React, { useLayoutEffect, useEffect, useState } from "react";
function About() {
  const [value, setValue] = useState(0);
  useEffect(() => {
    const divDom = document.getElementById("useLayout");
    console.log("color effect red", divDom);
  })

  useLayoutEffect(() => {
    const divDom = document.getElementById("useLayout");
    console.log("color layout effect red", divDom);
  })
  return (
    <div>
      <h2 onClick={() => setValue((c) => c + 1)}>useLayoutEffect的使用</h2>

      <div
        id="useLayout"
        style={{ color: value === 1 ? 'red' : '#999' }}
      >
        value:{value}
      </div>
    </div>
  );
}

export default About

效果图:
在这里插入图片描述
改变value触发DOM变化后,先打印‘color layout effect red’、‘color effect red’。

其实useEffect发生在浏览器绘制之后,而useLayoutEffect是发生在浏览器绘制之前。如果想要获取一个DOM上的属性值,一般是在useLayoutEffect中获取。例如clientHeight,它需要对DOM进行变更,在useLayoutEffect中获取,这一避免浏览器花费大量成本重排重绘。

另外useEffect运行时异步进行的,不会阻碍浏览器的渲染;useLayoutEffect则是同步运行,阻碍浏览器的渲染。

因为useEffect比较常用,所以再次回顾一下useEffect:

  1. useEffect与传统的类组件生命周期函数componentDidMount、componentDidUpdate和componentWillUnmount类似,但是不相同,因为useEffect是异步运行的,而componentDidMount、componentDidUpdate中的代码是“同步”执行【这里的同步指的是副作用执行会阻碍浏览器自身渲染】
  2. React通过requestAnimationFrame与postMessage来保证useEffect在浏览器绘制技术后执行。
  3. useEffect不允许用async函数作为回答函数。因为useEffect的回调函数返回一个函数,在组件卸载时执行一些逻辑。async函数返回的是一个promise,那么这时的async函数内返回的函数就不起作用。如果需设置async函数的话,只能在useEffect的回调函数内设置。

react函数编程Hook

hook在不编写class的情况下使用state,目的就是解决class中生命周期函数经常包含的不相关逻辑,相关逻辑有分离到不同方法中等这类问题

useState

定义一个“state变量”,

import React,{useState} from "react";
const [num,setNum]  =useState(0) 

与在this.state中定义的变量一样

useEffect

Effect Hook在函数组件中执行副作用操作

副作用:订阅、手动更改组件等操作。Effect Hook是componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合

useEffect在组件渲染后执行一些副作用操作,有需要删除的副作用,则返回一个函数;没有则不需要返回

如:

    useEffect(() => {
        document.title = `点击次数:${num}`
    })
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

同样是在组件渲染后执行一些副作用操作,class组件则需要拆分,如:

class FriendStatus extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isOnline: null };
    this.handleStatusChange = this.handleStatusChange.bind(this);
  }

  componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  handleStatusChange(status) {
    this.setState({
      isOnline: status.isOnline
    });
  }

  render() {
    if (this.state.isOnline === null) {
      return 'Loading...';
    }
    return this.state.isOnline ? 'Online' : 'Offline';
  }
}

性能优化

useEffect,只需要传递数组作为第二个参数即可。

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新

Hook 使用条件:

  1. 只在最顶层使用Hook,不能在循环,条件或者嵌套函数中使用Hook
  2. 不能在普通JavaScript函数中使用Hook函数

基础Hook:

  1. useState
const [num,setNum] = useState(0);

返回一个state,更新state的函数

  1. useEffect
useEffect(doUpdate)

useEffect(
  () => {
    const subscription = props.source.subscribe();
    return () => {
      subscription.unsubscribe();
    };
  },
  [props.source],
);

接受一个包含命令式、副作用操作的函数(代码)

  1. useContext
const value = useContext(MyContext)

额外的Hook

  1. useReducer

useState的替代方案,当state逻辑很复杂并且包含多个字值的时候,useReducer比useState更适合。

function init(initialCount) {
  return { count: initialCount };
}
const initialState = { count: 0 }
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return init(action.payload);
    default:
      throw new Error();
  }
}
function Counter({ initialCount }) {
  // const [state, dispatch] = useReducer(reducer, 1, init);
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button
        onClick={() => dispatch({ type: 'reset', payload: initialCount })}>
        Reset
      </button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
    </>
  );
}
  1. useCallback

参数为内联回调函数和依赖项数组,在某一个依赖项发生改变时,回调函数才会更新。返回memoized回调函数

 const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

 useCallback(fn,deps) <=> useMemo(()=>fn,deps)
  1. useMemo

返回一个memoized值,以“创建”函数和依赖项数组作为参数,某一个依赖项发生变化时,重新计算memoized值,没有传入依赖项时,useMemo在每一次渲染都会计算memoized的值。

const memoizedValue =  useMemo(()=>fn(a,b),[a,b])
  1. Ref
const refContainer = useRef(initialValue);

返回一个可变的ref对象
5. useImperativeHandle

useImperativeHandle 应当与 forwardRef 一起使用

useImperativeHandle(ref, createHandle, [deps])

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
  1. useLayoutEffect

在所有的DOM变更之后同步调用effect,读取DOM布局并同步触发重渲染。


  1. useDebugValue

可用于在 React 开发者工具中显示自定义 hook 的标签

react Hook 设置对应的周期

constructor ====> 使用useState来初始化state

componentDidMount ====> 使用useEffect传入第二参数为[]来实现;

componentDidUpdate ====> useEffect传入第二个参数为空或者是值为变动的数组;

componentWillUnmount ====> 清除副作用,在useEffectreturn一个函数来模拟

shouldComponentUpdate ====> 使用React.memo 包含一个数组对它的props浅比较

React的Context

Context的作用是在组件树间传递数据,不用在每一层组件手动添加props。Context主要的目的应该考虑应用的全局数据,就像当前的认证用户、主题等等数据。


React.createContext():创建一个Context对象

const TestContext = React.createContext("");


<TestContext.Provider   value ="值"></TestContext.provider>

每一个Context对象都会返回一个Provider React组件,它的作用就是允许组件订阅context的变化。Provider接收一个属性,传给子组件。

redux.store

import { createStore, combineReducers, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import allReducers from "@/reducer/reducer";

const reducers = combineReducers({
   
})


const initialState = {};
const middleware = [thunk];
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(reducers, initialState, composeEnhancers(
    applyMiddleware(...middleware)
))


export default store

react笔记

React在内存中创建一个虚拟的DOM,即为React-Dom,渲染器基于更改对浏览器DOM进行更新【只更新发生改变的部分】。

ReactElement

ReactDomElement,DOM元素的虚拟表示

ReactComponentElement,对应React组件的一个函数/类。

React.createElement(type,props,children):创建react元素

参数type,表示HTML元素标签,参数的形式是组件或者字符串;

参数props,表示定义元素属性,是一个属性对象;

参数children,子元素

createElement:简单表示就是创建什么、如何配置它、它包含什么。很多react组件就是react元素的集合。

高阶组件:不直接显示任何东西,而是修改或者增添其他组件的组件。

React类创建的组件拥有存储数据的支撑实例,只需返回单个react元素render。

state:程序在某个特定时间事物的信息。

state、props是React组件处理数据、通信的两种方式。

react通过this.state属性来访问可变状态,this.props属性来访问不可变状态;

state是react进行追踪的特殊属性,props是react传递不可变数据的主要方式。

React组件卸载是从DOM中移除组件的过程。

ComponentWillUnmount:监听组件卸载时的操作;

componentDidCatch(error,errorInfo):捕捉构造函数、渲染和生命周期方法中未捕获的错误;

当组件被移除之后,ComponentDidUnmount的生命周期就结束。

shouldComponentUpdate决定是否调用componentWillUpdate和componentDidUpdate。

ComponentWillMount挂载组件(即将挂载)

ComponentDidMount完成组件挂载【访问组件的状态属性、组件准备更新状态】。

react-redux

action、reducer、store。

store:状态来源

将action绑定到组件的action属性上

将组件链接到Redux store。将store作为属性传注入组件。

react-redux的connect工具函数,它会传入一个函数,mapSateToProps【接收一个状态作为参数,返回一个对象,与组件属性合并。第二参数mapDispatchToProps,将action绑定到组件上,该参数里面有一个dispatch函数,它就是注入到组件的dispatch方法。

reducer,是一个纯函数,负责计算store如何改变。在一个应用中出现多个reducer,分别负责store一个切片,使用Redux的combineReducer合并reducers成一个reducer

react Hook onclik()自动执行的问题

原始的写法是:onClik={copy(str)},原因是在声明onClik事件时就直接执行,之后将
返回值赋给onClik,所以再次点击无效。
更新写法:onClik = {()=>{copy(str)}}

React使用虚拟Virtual DOM

JavaScript的DOM树

DOM是结构化文本的抽象表达方式,在一个特定的web环境中它就是HTML文本,HTML中的每一个元素在DOM中有一个对应的节点,而且在HTML中的元素是存在包含关系,DOM节点就构成一个树型结构,称之为DOM树。

浏览器为了渲染和HTML格式的网页,会先把HTML文本解析为DOM树,再根据DOM树渲染出用户所看到的界面,当需要改变的界面的时候,就要修改DOM树上的节点。

浏览器渲染的步骤:
1、构建DOM树,display:none的元素也在DOM树中,只是被隐藏起来了;
2、构建CSSOM树;
3、构建渲染树,在DOM节点上找到对应的css规则进行适配;
4、渲染树布局,对节点的位置进行布局【absolute、fixed和float元素位置发生偏移的时候,这一类元素已经脱离渲染树,也就是文档流】;
5、渲染树绘制,也就是我们看到的页面。

所以在做网页性能优化的时候,尽量减少对DOM的操作。因为对DOM的每次操作都会导致浏览器布局和绘制网页



React使用虚拟Virtual DOM

然而react正好是使用Virtual DOM。react的jsx被Babel解析为创建React组件或者HTML元素的语句,并且react也没有通过这些语句来构建DOM树,而是直接构建虚拟DOM【虚拟DOM是对DOM树的抽象】。虚拟DOM不会触及到浏览器的部分,只是存在于JavaScript空间的树型结构,每一次自上而下渲染react组件时,会产生这次构建的虚拟DOM和上一次构建的虚拟DOM进行对比,在下一步骤修改真实的DOM树节点时,只更新发生改变的地方。

一个真实Element对象相当于虚拟DOM上的一个节点,具体参数:

1、tagName:元素名称;
2、props:元素属性【以键值对的方式存在】
3、children:该节点下的子节点数组


Virtual DOM转化为真实的DOM树


render函数:渲染视图,将虚拟的DOM转化为真实的DOM


store redux环境配置简写【代码】

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(reducers, initialState, composeEnhancers(
    applyMiddleware(...middleware)
))

设计高性能react组件的思路

对于一个高性能的react组件来说,减少重绘和重排、减少服务器渲染等很重要。

在一个react应用中,是通过react组件的形式来组织逻辑,把UI拆分为多个独立的、可复用的组件,那就恶意从组件的渲染角度来考虑组件性能优化:

  1. 使用shouldComponentUpdate来避免重复的更新逻辑
  2. 通过PureComponent + Immutable.js
  3. React.memo和useMemo

shouldComponentUpdate

shouldComponentUpdate是react类组件的一个生命周期,react组件是根据shouldComponentUpdate的返回值来判断是否对组件进行重新渲染的。

shouldComponentUpdate的默认值是true,现在有这么一个组件组合:
App组件包含组件A和组件B:

组件A:

import React from "react";
export default class ChilA extends React.Component {
    render() {
        console.log("ChildA的render执行了");
        return (
            <div>
                A组件的内容:
                {this.props.text}
            </div>
        )
    }
}

组件B:

import React from "react";
export default class ChildB extends React.Component {
    render() {
        console.log("ChildB的render执行了");
        return (
            <div>
                B组件的内容:
                {this.props.text}
            </div>
        )
    } 
}

App:

import { useState } from "react"
import ChildA from "./ChildA";
import ChildB from "./ChildB";
function Home() {
    const [textA, setTextA] = useState("A组件的文本");
    const [textB, setTextB] = useState("B组件的文本");

    const changeA = () => {
        setTextA("A组件的文本发生的改变");
    }
    const changeB = () => {
        setTextB("B组件文本发生改变了");
    }
    return <div className="App">
        <button onClick={changeA}>changeA</button>
        <button onClick={changeB}>changeB</button>
        <ul>
            <li><ChildA text={textA}/></li>
            <li><ChildB text={textB}/></li>
        </ul>
    </div>
}


export default Home 

在初次渲染的时候,A组件和B组件都会触发render,这是毋庸置疑的。

当我点击changeA的时候,A组件的文本发生改变了,因为changeA方法更新了textA,A组件的props发生改变了从而让A组件重新渲染一次。

但是组件B也重新渲染了:
在这里插入图片描述
虽然B组件的props没有发生改变。在react组件中,只要父组件的状态发生改变,那么它的子组件也都重新渲染一次。

在以上A和B,两个类组件中,虽然我们没有定义shouldComponentUpdate方法,但是它的默认返回值是true,也就是对更新环节没有做限制。所以就会导致组件做一个不必要的渲染。

那现在对B组件的shouldComponentUpdate生命周期做一些条件判断,在更新环节做个限制:

    shouldComponentUpdate(nextProps, nextState) {
        console.log(nextProps, nextState)
        if (nextProps.text === this.props.text) {
            return false
        }
        return true;
    }

这样就可以避免B组件发生不必要的渲染了。

PureComponent

shouldComponentUpdate可以实现组件优化,但是需要在每个组件手动做更新限制,似乎有点繁杂。

React.PureComponent和React.Component很相似,React.Component没有实现shouldComponentUpdate的功能, 而React.PureComponent通过浅比较props和state来实现shouldComponentUpdate的功能:

通过改造B组件后的C组件:

import React from "react";
export default class ChildC extends React.PureComponent {

    render() {
        console.log("ChildC的render执行了");
        return (
            <div>
                C组件的内容:
                {this.props.text}
            </div>
        )
    }
}

还有一种方式,明天再更新哈

Input

import React from "react"
import { Input } from "antd";
import { EllipsisOutlined } from '@ant-design/icons';

function InputDome() {
    const add = () => {
        console.log("可以了!");
    }
    return <React.Fragment>
        <div style={{ width: "200px" }}>
            <Input suffix={(<EllipsisOutlined onClick={add} />)} />
        </div>
    </React.Fragment>
}
export default InputDome

react元素

react元素是构成react应用的基本的块。它是创建开销很小的普通对象,并由React DOM更新DOM和react元素保持一致性。

React-router原理——浏览器记录

React-router在React应用中用来路由管理。

在理解React-router原理之前,我们先来简单了解一下几点知识:

  1. URL和URI
  2. 浏览器记录
  3. 浏览器中的跳转和相关事件

URI和URL

URI:统一资源标识符,允许用户对网络中的资源通过特定的协议进行交互操作。
URL:统一资源定位器,作为URI其中的一种。

浏览器记录

是浏览器中各个页面的用户导航记录,通过window.history.length获取页面栈的信息。浏览器的记录是浏览器统一管理的,它不属于具体的某一个页面。

window.history上有何属性,比如window.history.pushState、window.history.replaceState和popstate,可以在页面栈上添加和修改记录。

浏览器中的跳转和相关事件

在histor对象上有一些方法可以操作页面栈记录,比如通过移动栈指针或者添加栈记录进行导航跳转。

window.history.go(),加载页面栈中的某个具体的页面,来完成用户在页面栈中向前或者是向后跳转;

window.history.forward(),栈指针向前移动一个位置,和history.go(1)同等;
window.history.back(),栈指针后移动一个位置,浏览器后跳。

window.location.href,页面栈产生新的一个记录,和window.location一个结果,例如:window.location = 'http://qingzhuyue.cn'等价window.location.href = 'http://qingzhuyue.cn',刷新页面并且加载URL指定的内容。

window.location.hash,hash以‘#’开头,通过设置window.location.hash可改变hash的值。
window.location.replace,替换当前页面栈的记录,会刷新页面,重新加载传入的URL。


popstate事件

在移动页面栈指针或者点击浏览器的前进或后退时,会触发popstate事件,可以通过window.addEventListener监听该事件。

window.addEventListener('popstate',function(event){})

在调用history.pushState或者history.replace不会触发popstate方法,但是在单击浏览器的前进或者后退、调用history的go、back、forward、修改浏览器的hash就会触发popstate,从而获取到当前的state对象。

页面栈中的state对象是深拷贝存储在浏览器中,不论进行导航切换还是刷新当前页面,页面栈的内容都会存在刽被销毁。

前后两次设置相同的location.hash,不会触发两次popstate事件

如果通过location.href来设置hash值,不管前后两次的hash值是否相同,都会触发popstate事件。如果前后两次设置的hash值相同的时候,只会添加一个历史记录。

hashchange

监听浏览器hash值的变化,

window.addEventListener('hashchange',function(event){});

在设置location.hash、手动修改hash值、调用window.history.go、单击浏览器的前进或者后退,会触发hashchange事件。

关于浏览器记录的暂时记录到这里了。

useState和useEffect

useState()

useState为组件状态注入和修改状态的方法。

useEffect()

为函数组件引入副作用,在类组件中,所有像操作DOM、订阅事件、调用外部API获取数据等等,都是放在componentDidMount、componentDidUpdate和componentWillUNmount生命周期中来实现的。
但是在函数组件中,就可以在useEffect中完成。

import { useState, useEffect } from "react"
function App() {
  const [data, setData] = useState(0);
  useEffect(() => {
    const todoList = document.getElementById("todoList");
    const newItem = document.createElement("li");
    newItem.innerHTML = `这是第${data}个TODO`;
    todoList.append(newItem)
  })
  const add = () => {
    setData(data + 1)
  }
  return <>
    <p>当前共`${data}`个TODO Item</p>
    <ul id="todoList"></ul>
    <button onClick={add}>add</button>
  </>
}

useEffect可以接受两个参数,一个是回调函数和依赖数组:

useEffect(callback,[])

useEffect调用的一些规则:

  1. 每次渲染后都执行副作用:只需传入回调函数即可,不需要依赖数组
useEffect(callback)
  1. 仅仅在挂载阶段执行一次副作用,传入回调函数,该函数的返回值不是一个函数,而是一个值。同时传入一个空的数组
useEffect(()=>{
        // 业务逻辑
},[]);

import React, { useState, useEffect } from "react"
export default function Child() {
    const [count, setCount] = useState(0)
    useEffect(() => {
        console.log("挂载阶段")
        setInterval(() => {
            setCount(count + 1)
        }, 1000)
        return ()=>{
            console.log("卸载阶段")
        }
    })
    return <div>
        这是子组件{count}
    </div>
}
  1. 在挂载阶段和卸载阶段执行副作用:传入回调函数、并且该函数返回一个空函数,同时传入一个空数组:
useEffect(()=>{
  // 业务逻辑
  return ()=>{}
},[])

会在卸载阶段去执行返回的函数,可以称之为“清除函数”。
4. 每一次渲染都回触发,并且在卸载阶段触发副作用:传入回调函数,该函数的返回值是一个函数,不传入依赖数组;

useEffect(()=>{
  // 业务A逻辑
  
  return ()=>{} // B逻辑
})

这个方式会在每一次渲染都触发A逻辑,在卸载阶段触发B逻辑。
5. 根据依赖条件来触发副作用:传入回调函数,同时传入一个非空数组

useEffect(()=>{
        // 业务逻辑
  
  
},[num1,num2])

这个数组一般是来源于props或者state

react中引入echarts,源码

import React, { useEffect, useState } from "react";
// 引入 ECharts 主模块
import echarts from 'echarts/lib/echarts';
// 引入柱状图
import 'echarts/lib/chart/bar';
// 引入提示框和标题组件
import 'echarts/lib/component/tooltip';
import 'echarts/lib/component/title';
import "./index.css"
import { urls } from "../../utils/utils";
import { Spin, message } from "antd";
//渲染图形
const myChart = (xData, yData) => {
    const myChart = echarts.init(document.getElementById("main"));
    myChart.setOption({
        baseOption: {
            tooltip: {
                trigger: "axis",
                // 自定义tooltip
                formatter: function (params) {
                    var res = '<p>X轴类目:' + params[0].name + '</p>'
                    for (var i = 0; i < params.length; i++) {
                        res += '<p> Y轴数据:' + params[i].data + '</p>'
                    }
                    return res;
                },
            },
            color: "#00A0E8",//柱状图的颜色
            grid: {
                left: '2%',
                //距离左边的距离
                right: '1%', //距离右边的距离
                bottom: '10%', //距离下边的距离
                top: '8%', //距离上边的距离
            },
            xAxis: {
                type: 'category',
                data: xData,
                "axisLine": {       //X轴
                    "show": true,
                    lineStyle: {
                        color: "rgba(0,0,0,0.25)",
                    }
                },
                "axisTick": {       //轴刻度线
                    "show": false
                },
                "splitLine": {     //网格线
                    "show": false
                },
                axisLabel: {
                    interval: 4,
                    formatter: function (e) {
                        return e
                    },
                    textStyle: {
                        color: "rgba(0,0,0,0.25)",  //更改坐标轴文字颜色
                        fontSize: 16      //更改坐标轴文字大小
                    },
                }
            },
            yAxis: {
                title: "wge",
                type: 'value',
                axisLabel: {
                    //隐藏Y轴数据
                    // formatter: function () {
                    //     return "";
                    // }
                    textStyle: {
                        color: "rgba(0,0,0,0.25)",  //更改坐标轴文字颜色
                        fontSize: 16      //更改坐标轴文字大小
                    }
                },
                dispaly: "none",
                "axisLine": {       //y轴
                    "show": true,
                    lineStyle: {
                        color: "rgba(0,0,0,0.25)",
                    }
                },
                "axisTick": {       //y轴刻度线
                    "show": false
                },
                "splitLine": {     //网格线
                    "show": false
                },
                "show": true,
            },
            series: [{
                data: yData,
                type: 'bar',
                smooth: true,
                symbol: "none",
                barWidth: 20
            }]
        },
        media: [{
            query: { maxWidth: 959 },
            option: {
                grid: {
                    left: '6%',
                    //距离左边的距离
                    right: '1%', //距离右边的距离
                    bottom: '10%', //距离下边的距离
                    top: '8%', //距离上边的距离
                },
                xAxis: {
                    axisLabel: {
                        interval: 8,
                        textStyle: {
                            color: "rgba(0,0,0,0.25)",  //更改坐标轴文字颜色
                            fontSize: 10     //更改坐标轴文字大小
                        }
                    }
                },
                yAxis: {
                    axisLabel: {
                        textStyle: {
                            color: "rgba(0,0,0,0.25)",  //更改坐标轴文字颜色
                            fontSize: 10      //更改坐标轴文字大小
                        }
                    }
                },
                series: [{
                    symbol: "none",
                }]
            }
        }, {
            query: { maxWidth: 400, },
            option: {
                grid: {
                    left: '10%',
                    //距离左边的距离
                    right: '1%', //距离右边的距离
                    bottom: '10%', //距离下边的距离
                    top: '8%', //距离上边的距离
                },
            }
        }]
    });
    myChart.on("click", function (e) {
        console.log(e)
        console.log(e.name)
    })
};

function ApiTime() {
    const [spinning, setspinning] = useState(true)
    useEffect(() => {
        getData();
        const timer = setInterval(() => {
            getData();
        }, 10000);

        return () => { clearInterval(timer) }
    }, []);
    //监听页面变化重置
    window.addEventListener("resize", (e) => {
        // window.location.reload();

    })

    const getData = () => {
        fetch(urls)
            .then(res => res.json())
            .then(res => {
                const xData = [];
                const yData = [];
                if (res.Desc === "SUCCESS") {
                    setspinning(false)
                    res.Result.map((item) => {
                        xData.push(item);
                        yData.push(item);
                    })
                    myChart(xData, yData)
                } else {
                    message.error(res.Desc)
                }
            }).catch(err => {
                console.log(err)
            })
    }
    return <Spin spinning={spinning}><div id="main"></div></Spin>
}

export default ApiTime

redux简单的笔记

将近半年没有写react了,关于redux状态什么的,感觉自己都忘得差不多,今天就做一个简单的回顾笔记吧!

redux的3个核心部分:

store,单一的数据源;
Reducer,作为一个函数,它负责对数据变化的分发和处理;
action,对数据变化的描述。

combineReducers:合并多个reducer函数,

compose:把接受到的函数从左到右进行组合




createStore:接受三个参数,分别是reducer、初始状态的内容和指定的中间件;




dispatch:将redux的三大要素串联起来【store、action和reducer】




reducer的本质是store的更新规则,明确了应用状态的变化是如何响应action并且发送到store。

[React-Redux的connect](http://qingzhuyue.cn/archives/716)

[redux、react-redux状态管理应用](http://qingzhuyue.cn/archives/1051)

react组件状态共享或者传值

这篇博客主要是为了了解react  hook的useContext API的使用,顺便回忆一下react组件传值的方式!

以下有两种方式实现组件共享状态或者传值:

一、使用useContext()实现组件共享状态或者传值,useContext()接收React.createContext()返回的context对象值,当前的context对象值是上层组件【父组件】中距离当前组件最近的<MyContext.Provider>的value决定的。

当多个子组件需要共享一个状态或者一个值的时候,就可以在<MyContext.Provider>组件中设置相同的状态或者值,从而实现组件状态共享或者组件传值。

首先使用React的Context API在组件外面创建一个Context。

const reactContext = React.createContext({});

在父组件中用<reactContext.Provider>将子组件包裹起来,

      <reactContext.Provider
        value={{
          name: "组件状态共享!!!!!!!!!"
        }}>
        <Component1 />
        <Component2 />
      </reactContext.Provider>


子组件中用useContext来接收父组件context的value值,代码如下:

import React, { useContext } from "react";
import { reactContext } from "./App"
function Component1(props) {
    const { state } = useContext(reactContext)
    return <div>
        <p>组件A状态:{state}</p>
    </div>
}

export default Component1


注意:useContext的参数必须是context对象本身,另一个就是useContext的参数必须是父组件创建的context,所以在子组件中引入父组件创建的Context:

import { reactContext } from "./App";


App.jsx:

import React from 'react';
import Component1 from "./component1";
import Component2 from "./component2"
export const reactContext = React.createContext({});
function App() {
  return (
    <div className="App">
      <reactContext.Provider
        value={{
          name: "组件状态共享!!!!!!!!!"
        }}>
        <Component1 />
        <Component2 />
      </reactContext.Provider>
    </div>
  );
}

export default App;


component1.jsx:

import React, { useContext } from "react";
import { reactContext } from "./App"
function Component1(props) {
    const { state } = useContext(reactContext)
    return <div>
        <p>组件A状态:{state}</p>
    </div>
}

export default Component1


二、在多个子组件共享一个状态值的时候,可以向其传递相同的参数即可。在父组件渲染子组件的时候直接向子组件传参数值:

import React from "react";
import Component1 from "./component1";
import Component2 from "./component2";
function App() {
    return <div>
        <Component1 name="父组件传给子组件的参数值" />
        <Component2 value="父组件传给组件B的参数为111111" />
    </div>
}


export default App


在子组件的props内获取父组件传递过来的参数值:

Component1.jsx:

import React from "react";
function Component1(props) {
    console.log(props)
    return <div>
        <p>这是父组件传递过来的参数值:{props.name}</p>
    </div>
}


Component2.jsx:

import React from "react";
function Component2(props) {
    console.log(props)
    return <div>
        <h2>组件B:{props.value}</h2>
    </div>
}


export default Component2

react路由的高阶组件PrivateRoute

import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { fakeAuth } from '../utils/fake-auth';
const PrivateRoute = ({ component: Component, ...rest }) => {
    return (
        <Route
            {...rest}
            render={props => (fakeAuth.authenticate()) ? (<Component {...props} />) : (
                <Redirect to={{
                    pathname: "/login",
                    state: { from: props.location }
                }} />
            )}
        ></Route>
    )
}
export default PrivateRoute;

useReducer

/*
 * @Author: [email protected]
 * @Date: 2021-08-25 15:12:55
 * @LastEditTime: 2021-08-25 15:55:36
 * @LastEditors: [email protected]
 * @Description: 
 * @FilePath: \test\src\App.js
 */
import { useState, useReducer } from "react"
import './App.css';
function reducer(state, action) {
  switch (action.type) {
    case 'change':
      return {
        ...state,
        name: action.params.name
      }
    default:
      throw new Error();
  }
}
function App() {
  const initialState = { id: "", name: "duxin", age: "18" };
  const [state, dispatch] = useReducer(reducer, initialState);
  const changeInput = ({ target: { value } }) => {
    dispatch({type:"change",params:{name:value}})
  }
  return (
    <div className="App">
      <input onChange={changeInput} />

      <div>{state.name}</div>
    </div>
  );
}

export default App;

react、echarts面积图表

import { useEffect } from "react";
import * as echarts from 'echarts';
function AreaEchart() {
    useEffect(() => {
        const day = ["第1天", "第2天", "第3天", "第4天", "第5天", "第6天", "第7天", "第8天", "第9天", "第10天", "第11天", "第12天", "第13天", "第14天", "第15天"], data = [100, 6, 54, 9, 5, 5, 12, 8, 32, 20, 11, 12, 43, 14, 70]
        var myChart = echarts.init(document.getElementById('areaEchart'));
        myChart.setOption({
            tooltip: {
                trigger: "axis",
                axisPointer: {
                    type: "",
                },
                formatter(params) {
                    for (let x in params) {
                        return (
                            params[x].name +
                            ":" +
                            params[x].data
                        );
                    }
                },
            },
            grid: {
                left: 0,
                right: 0,
                bottom: 30,
                top: 0
            },
            xAxis: {
                axisLabel: {
                    formatter: function () {
                        return "";
                    }
                },
                "axisLine": {       //y轴
                    "show": false
                },
                "axisTick": {       //y轴刻度线
                    "show": false
                },
                "splitLine": {     //网格线
                    "show": false
                },
                show: false,
                data: day,
                boundaryGap: false,
            },
            yAxis: {
                show: false,
                axisLabel: {
                    formatter: function () {
                        return "";
                    }
                },
                "axisLine": {       //y轴
                    "show": false
                },
                "splitLine": {     //网格线
                    "show": false
                },
                "axisTick": {       //y轴刻度线
                    "show": false
                },

            },
            series: [
                {
                    type: 'bar',
                    barWidth: '10',
                    color: "#2390fa",
                    data: data,
                    type: 'line',
                    areaStyle: {
                        color: {
                            type: 'linear',
                            x: 0,
                            y: 0,
                            x2: 0,
                            y2: 1,
                            colorStops: [{
                                offset: 0, color: '#2390fa' // 0% 处的颜色
                            }, {
                                offset: 1, color: '#000' // 0% 处的颜色
                            }],
                            global: false // 缺省为 false
                        }
                    },
                    smooth: true,
                    showSymbol: false,
                    lineStyle: {
                        width: 0,//外边线宽度
                    },
                }
            ]
        });
    }, [])
    return <div id="areaEchart" style={{ width: "400px", height: "100px" }}></div>
}

export default AreaEchart

react生命周期

react的生命周期

react生命周期分为三个阶段:初始化阶段、运行阶段和销毁阶段。

初始化阶段

在这个阶段,初始化组件的状态,执行的是构造函数constructor;

componentWillMount(),组件即将渲染时执行,一一般这时候开始设置定时器、向服务器发送请求等操作;

render(),组件渲染;

componentDidMount(),组件渲染后需要操作的事件,都放在这个时候执行;

运行阶段

componentWillReceiveProps(),组件在接收到组件的时候,可以触发一些操作;

shouldComponentUpdate(),组件接收到新属性的时候触发;

componentWillUpdate(),组件即将更新的时候,可做一些操作;

componentDidUpdate(),组件更新后可操作事件;

销毁阶段

componentWillUnmount(),组件卸载时,可以做一些操作。

Hooks

useState为组件设置状态和修改状态的方法。

useEffect(),给函数组件引入副作用,在类组件中像操作DOM、

[03]react的jsx语法介绍和使用

官方的描述:JSX是一个 JavaScript 的语法扩展。我们建议在 React 中配合使用 JSX,JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式。JSX 可能会使人联想到模版语言,但它具有 JavaScript 的全部功能。它没有任何语义,专门提供给与处理器使用。

const  dom = <div>这是一个jsx语法使用.</div>

在react中声明组件的时候使用jsx,如以下声明的组件:

import React from "react";
class ClassComponent extends Reacrt.Component {

    render() {
        return <div>
            在声明组件的时候使用jsx语法
   </div>
    }
}


export default ClassComponent

redux对应hook的API:useDispatch,useSelector

redux,本身只是一个工具而已,至于用不用,什么时候用,都在于开发者和团队。
就算是不用redux,同样也可以实现我们想要的功能和效果。但是,作为工具,它的诞生并且受到开发者的欢迎,就有它存在的理由:存在即合理!

一般在组件拆分足够细的时候,为了避免数据在组件之间层层传递,我们会把数据放在整个应用的上下文环境中,其他组件可以获取对应的数据。

这有点像发布-订阅模式。

一下就是react组件引入Redux:
store.js

import { createStore, combineReducers, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import { allReducers } from "./reducer";

const reducers = combineReducers({ allReducers })

const middleware = [thunk];
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(reducers, composeEnhancers(
    applyMiddleware(...middleware)
))


export default store

App.jsx

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import reportWebVitals from './reportWebVitals';
import ReduxComponent from "./components/ReduxComponent"
import store from "./store";
import { Provider } from "react-redux"
ReactDOM.render(
  <Provider store={store}>
    <ReduxComponent />
  </Provider>,
  document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

ReduxComponent.jsx:

import React from "react";
import ReduxChild from './ReduxChild';
import { connect } from "react-redux"
const styles = {
    span: {
        display: "inline-block",
        width: "30px",
        height: '30px',
        textAlign: 'center',
        borderRadius: '2px',
        border: '1px solid green',
        marginLeft: '5px'
    }
}
function ReduxComponent(props) {
    console.log("props", props)
    return <div>
        <span style={styles.span} onClick={() => props.increment(props.num)}>+</span>
        <span style={styles.span} onClick={() => { props.decrement(props.num) }}>-</span>

        <p>父组件:{props.num}</p>
        <ReduxChild />
    </div>
}
const mapStateToProps = (state) => {
    console.log("props", state)
    return state.allReducers
}
const mapDispatchToProps = (dispatch) => ({
    increment: value => dispatch({ type: 'increment', value: value }),
    decrement: value => dispatch({ type: 'decrement', value })
})
export default connect(mapStateToProps, mapDispatchToProps)(ReduxComponent)

现在的ReduxComponent组件是通过redux的connect来链接的。connect有两个参数:mapStateToProps和mapDispatchToProps,分别是将state和action转化到组件的Props。需要我们定义定义这两个参数,因为在store上面是存在整个应用的所有数据和状态,通过mapStateToProps和mapDispatchToProps来筛选本组件相关的状态和action。

useDispatch,useSelector

import React from "react";
import ReduxChild from './ReduxChild';
import { useDispatch, useSelector } from "react-redux"
const styles = {
    span: {
        display: "inline-block",
        width: "30px",
        height: '30px',
        textAlign: 'center',
        borderRadius: '2px',
        border: '1px solid green',
        marginLeft: '5px'
    }
}
function ReduxComponent() {
    const dispatch = useDispatch();
    const reduxProps = useSelector(state => {
        return state.allReducers
    })
    return <div>
        <span style={styles.span} onClick={() => dispatch({ type: 'increment', value: reduxProps.num })}>+</span>
        <span style={styles.span} onClick={() => { dispatch({ type: 'decrement', value: reduxProps.num }) }}>-</span>

        <p>父组件:{reduxProps.num}</p>
        <ReduxChild />
    </div>
}
export default ReduxComponent

现在react-redux提供对应的hookAPI,在组件中我们直接获取对应的state和action了。

react富文本编辑器

import React from 'react'
// 引入编辑器组件
import BraftEditor from 'braft-editor'

import CodeHighlighter from "braft-extensions/dist/code-highlighter"
// 引入编辑器样式
import 'braft-editor/dist/index.css'
import 'braft-extensions/dist/code-highlighter.css'
import "./App.css"

BraftEditor.use(CodeHighlighter({
  includeEditors: ['editor-with-code-highlighter'],
}))
export default class App extends React.Component {
  state = {
    // 创建一个空的editorState作为初始值
    editorState: BraftEditor.createEditorState(null),
    defualtContent: "<div></div>"
  }
  async componentDidMount() {
    // 假设此处从服务端获取html格式的编辑器内容
    // const htmlContent = await fetchEditorContent()
    // 使用BraftEditor.createEditorState将html字符串转换为编辑器需要的editorStat
    const sss = BraftEditor.createEditorState("");
    console.log(sss)
    this.setState({
      editorState: BraftEditor.createEditorState("")
    })
  }

  submitContent = async () => {
    // 在编辑器获得焦点时按下ctrl+s会执行此方法
    // 编辑器内容提交到服务端之前,可直接调用editorState.toHTML()来获取HTML格式的内容
    // const htmlContent = this.state.editorState.toHTML();
    // this.setState({
    //   defualtContent:htmlContent
    // })
  }

  handleEditorChange = (editorState) => {
    const htmlContent = editorState.toHTML();
    this.setState({
      editorState,
      defualtContent: htmlContent
    })
  }

  render() {
    const { editorState, defualtContent } = this.state;
    return (
      <div className="content">
        <div className="my-component">
          <BraftEditor
            value={editorState}
            onChange={this.handleEditorChange}
            onSave={this.submitContent}
          />
        </div>
        <div className="review"  dangerouslySetInnerHTML = {{ __html:defualtContent }}></div>
      </div>
    )
  }
}

react学习的目标和目录

生命周期:

componentWillMount

componentDidMount

componentWillReceiveProps

shouldComponentUpdate

componentWillUpdate

componentDidUpdate
 
componentWillUnmount

错误处理

componentDidCatch(): 


react 16 的Portals特性,将组件渲染到当前组件树以外的DOM节点上。例如应用的全局弹框。

ReactDOM.createPortal(child,container)

child:被渲染的react元素
container:一个DOM元素,child被挂载到该DOM元素上。




组件的state、组件与服务器通信、组件通信、组件的ref属性


ref获取表单元素,或者获取其他元素,也可以获取React组件实例;

性能优化:使用shouldComponentUpdate确定组件是否重新渲染。



路由:react-router-dom

Router、Route

Router是路由器,一个应用只需要一个Router实例。而所有的路由配置组件Route定义为Router的子路由。



一般使用对Router进行包装了的BrowserRouter或者是HashRouter组件。

BrowserRouter使用了HTML5的history API来完成UI和URL的同步,需要对服务器进行配置,让服务器正确处理所有可能的URL。http://qingzhuyue.cn/path1和http://qingzhuyue.cn/path2是两个请求,要让服务器返回正确的HTML页面。如果没有进行配置的话,当进行路由挑战或者

HashRouter使用了URL的hash完成应用的UI和URL的同步。真正有效的信息是hash前面的部分,而对于单页面应用来说,这部分内容是固定的。

Switch和exact

react-hook的理解

类组件和函数组件:

类组件是基于ES6 Class的写法,通过继承React.Component得到的react组件。

函数组件,就是以函数的形式存在的react组件。

两种组件的区别:

1、类组件需要继承class,函数组件不需要;
2、类组件可以访问生命周期,函数组件不可以;
3、类组件可以获取实例话后的this,并基于this做其他很多的事情,而函数组件不可以;
4、类组件可以维护state,函数组件不可以。

类组件是面向对象编程的一种表现,react类组件内部预置很多的API,我们直接调用/定制,就像生命周期,只要我们继承一个React.Component就可以了。

但是React类组件呢,它给我们的东西是太多了,有多少我们就需要学习多少。有时候,“多”并不意味着“好”。例如生命周期,如果记不住生命周期,那么在组件逻辑中有可能会变成一团糟。最后总结就是,React组件的“大而全”,增加我们的学习成本。

另外在某些场景下,React组件显得太重了,

类组件内的逻辑封装之后是和组件粘在一起的,难以拆分和复用(可以使用高阶组件、Render Props来实现)。

综合下来就是React类组件就是很强大,但并非全能。

函数组件:响应了React的设计理念“轻巧”,它和类组件一样可以承接相对复杂的交互逻辑。

函数组件显而易见的一个特质就是轻量、灵活,易于组织和维护、较低的学习成本。

函数组件和类组件最大的不同就是,函数组件可以捕获render内部的状态。

React组件的本身的定位是函数,一个吃进数据,吐出UI的函数。React框架只要的工作就是将代码转化为DOM操作,把数据层面的描述映射到用户可见的UI变化中去。React的数据应该是紧紧和渲染绑定一起的,而类组件则不能做到这一点。
因为组件之间的数据传输是通过props来实现的,在类组件中是通过this.props 来获取数据,虽然props本身不可变的,但是this是可变的,this上的数据也是可以修改的,所以this.props每次调用都会获取最新的props,这是react保证数据实时性的一个保证。

但是在某些场景下,我们通过setTimeout将渲染推迟3s,酒打破了this.props和渲染动作之间的联系了,这就导致了渲染错误。
然而在函数组件中,是直接通过props接受数据,它会在函数组件执行的瞬间就被捕获,而且props本身就是不可变的,所以在任何时机下获取props,都是最初捕获到那个props。当父组件传一个新的props来重新渲染组件的时候,本质上就是基于新的props入参而发起的一次全新函数调用,并不会影响上次对props的捕获。这就前面为什么说是函数组件可以捕获render内的状态的原因。

Hook的本质就是:一套能够使用函数组件更强大、更灵活的“钩子”

在函数组件中没有生命周期和state的说法:

useState():为函数组件引入状态,通过useState()可以直接在函数组件引入state。

useEffect():允许函数组件执行副作用的操作,在一定程度上弥补了react函数组件没有生命周期的遗憾。

useEffect()的使用规则:

  1. 每一次渲染后都执行的副作用,传入回调函数,不依赖数组;
  2. 只在挂载阶段执行一次的副作用,传入回调函数且该函数返回值不是一个函数,同时传入一个空数组;
  3. 在挂载阶段和卸载阶段执行的副作用,传入一个回调函数且该函数的返回值是一个函数,同时要传入一个空的数组,挂载阶段执行的是回调函数,卸载阶段执行返回的函数;
  4. 每次都触发且卸载阶段也会被触发的副作用:传入回调函数且该函数的返回值是一个函数,不需第二个参数;
  5. 根据一定的依赖条件来触发的副作用:传入回调函数【返回值是一个函数的话,依然会影响到卸载阶段对副作用的处理】和一个非空的数组。数组的变量来自组件本身的数据(props或者state),数组不为空,React在新的一次渲染后 去对比前后两次的渲染,查看数组内的变量是否发生变化(只要有一个变量发生变化就会处方更新),在由更新的前提下去触发useEffect中定义的逻辑

对于开发者而言 Hook 的优势是:

  1. 告别难以理解的 class,因为在 class 组件中,含有 this 和生命周期这两个痛点,很多时候不太容易理解,如下代码:
import React, { Component } from "react";
class Home extends Component {
    state = {
        name: "duxin",
        age: 11
    }
    changeInfo() {
        this.setState({
            age: 19
        })
    }
    render() {
        return <button onClick={this.changeInfo}>{this.state.name}的年龄是{this.state.age}</button>
    }
}
export default Home;

在点击 button 的时候,changeInfo 函数中的 this 没有捕获实例的 this,当然也可以通过 bind、箭头函数来解决这个问题,这些手段的本质是在实践层面来解决设计层面的问题
转化为函数组件,代码:

import React, { useState } from "react";
function Home() {
    const [name, setName] = useState("duxin");
    const [age, setAge] = useState(11);
    const changeInfo = function () {
        setAge(90)
    }
    return <button onClick={changeInfo}>{name}的年龄是{age}</button>
}
export default Home;
  1. Hook 能够帮助我们实现业务逻辑的聚合,避免复杂的组件和冗余的代码;
  2. Hooks 将复杂问题简单化。

Hooks 的局限性

  1. Hooks 暂时还不能为函数组件补齐类组件的能力,如: getSnapshotBeforeUpdate、componentDidCatch 这些生命周期;
  2. 使用函数组件来解决相同的问题,在业务逻辑拆分和组织会是一个很大的挑战;
  3. Hooks 在使用层面会有很多严格的规则约束。

路径简写@【代码】

/*
 * @Author: [email protected]
 * @Date: 2021-08-17 11:00:31
 * @LastEditTime: 2021-08-17 11:11:09
 * @LastEditors: [email protected]
 * @Description: 路径简写配置
 * @FilePath: \xiyou-knit-web3333\craco.config.js
 * 
 */
const path = require('path')
const resolve = dir => path.resolve(__dirname, dir)

module.exports = {
    webpack: {
        alias: {
            "@": resolve("src"),
        }
    }
}

【01】使用create-react-app创建react应用,快速体验react开发

最快体验react,不需要进行繁琐的配置,使用create-react-app创建react应用。如果电脑上没有安装node的话,需要重新安装node的环境,node本身自带的npm,直接使用即可。

1、安装create-react-app:

npm install -g create-react-app

查看create-react-app的版本:

create-react-app --version


2、使用create-react-app创建react应用:

create-react-app myApp

在创建的过程中,create-react-app会自动安装一些简单的依赖:

react、react-dom

切换到新创建的react应用目录下:
cd myApp


使用命令运行react应用
  npm start





react组件和props接收参数

在react应用中,可以使用两种方式来定义组件:函数和class类,

函数组件:

import React from "react";
function Func_component(props) {
    return <div>
        <h2>这是一个函数组件:</h2>
        <p>{props.name}</p>
    </div>
}

export default Func_component

class类组件:

import React from "react";

class Class_Component extends React.Component {
    render() {
        return <div>
            <h2>这是一个react的class组件</h2>
            <p>{this.props.name}</p>
        </div>
    }
}


export default Class_Component


当react应用在渲染我们所定义的组件之后,react会把jsx接收到的属性或者子组件转化为单个对象传递给组件,这个对象就是“props”。props只是用来接收父组件传给子组件的参数而已,它并不是组件本身自己的数据,所以组件的Props具有不可更改的特征,这就是react的一个规则:所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。

组件如何接收接收props?

先说我们常见的函数组件,和我们在刚开始学习编程的时候一样,我们在定义一个方法的时候,根据需求会接收一些参数。那么react应用中,这样的方法就是“组件”。同样,在函数组件中接收父组件传递过来的参数也需要定义些“形参”,通过形参来接收父组件传过来的“实参”。如以上的函数组件代码中,在定义组件时,设置形参“props”,它是一个包含了组件属性和子组件的一个对象。

在class组件内直接通过this.props来接收父组件传过来的参数值。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.