react

289 阅读6分钟

1. 安装

    1. npm install -g webpack
    1. npx create-react-app my-app
    1. cd my-app
    1. npm start
    1. npm i antd
  • css modules
  • npm i --save-dev react-router-dom
    注释:{/** **/}

2. 文档内容

2.1 在react中ReactDom相当于document,只不过是操作虚拟DOM,跟vue的区别:vue操作真实DOM,效率慢。但是React操作虚拟DOM

2.2

  • ReactDOM.render(arg1, arg2)
  • arg2: docukment.getElemntById("root") 在root下渲染

2.3. es5定义类

function A(name) {
    this.name = name
}
const a = new A('lixin')

es6定义类

class P {
constructor(name)
}
const p = new P('lixin')

2.4 vscode添加插件

setting->include language -> javascript javascriptreact triiger-> 打勾使用tab键

2.5 空标签<></>

react中必须要有根元素,可以用空标签。无法用className等属性

2.6 htmlFor和id一起,有联动效果

<label htmlFor="name">name</label>
<input id="name" />

2.7 虚拟Dom为什么效率高于真实

虚拟Dom的携带属性数量小于真实DOm

2.8 循环 只可以用map, 要有key

return (
    <>
        {
            arr.map(item, index) => {
                <li key={index}>{item}</li>
            })
        }
    </>
)

2.9 单纯修改变量无法触发render函数,所以用state,必须用在class定义里面,setState修改,可以简写,setState是异步

state = {
    name: 'lixin'
}

<h1>{this.state.name}</h1>

// 想等异步结束后
this.setState({
    name: 'han'
}, () => {
    console.log(this.state.name) // 获取到修改后的值
})

2.10 实现v-model

import React, { Component } from 'react'

export default class App extends Component {
  state = {
    msg : "Hello world"
  }
  change = function(e) {
    console.log(this)
    this.setState({
      msg: e.target.value
    })
  }
  render() {
    return (
      <div>
        <input type="text" value={this.state.msg} onChange={this.change.bind(this)}/>
        <p>{this.state.msg}</p>
      </div>
    )
  }
}

2.11 修改数组时,只可以用map (item,index) => ()

2.12 import PropTypes from 'prop-types'

指定props类型和必填,可以用在class、function组件中

import "./styles.css";
import PropTypes from "prop-types";

export default function App(props) {
    return (
        <div className="App">
            <h1>{props.number}</h1>
            <h2>Start editing to see some magic happen!</h2>
        </div>
    );
}
App.propTypes = {
    number: PropTypes.number.isRequired
};
// 其他类型 https://zh-hans.reactjs.org/docs/typechecking-with-proptypes.html
// 配置默认props
/**
上述的propTypes方法写在了外部,如果想写在内部,可以使用static
static: 静态方法,即只可以类调用,而实例调用不了 App.a可以 app.a不可以
可以写成下面
*/
export default class App extends Component {
    static propTypes = {
        number: PropTypes.number.isRequired
    };
    static defaultProps = {
        number: 11
    };
    render() {
        return (
            <div className="App">
                <h1>{this.props.number}</h1>
                <h2>Start editing to see some magic happen!</h2>
            </div>
        );
    }
}

2.13 如果函数中用到了this,需要改变this指向

2.14 传值

2.14.1 父传子 props

  • props
1. 在构造器中是否需要传递super,有两种情况
/**
1. props可传可不传,但是如果,这个时候使用this.props就会出现问题
2. contructor的作用,使用this.state和修改函数this指向
3. 但是第二条可以使用state、箭头函数替代,所以构造器可写可不写
*/
constructor() {
    super() 
    console.log(this.props)// undefined
}

2.14.2 子传父 跟vue一样,没有$emit就是

2.14.3 跨级传值 context

// 父组件
1. 定义传入数据类型
static childContextTypes = {
    num: PropTypes.number
}
2. 传入值赋值
getChildContext() {
    return {
        numL 666
    }
}
// 孙组件
3. 定义接受数据类型
static contextTypes = {
     num: PropTypes.number
}
4. 接收值
this.context.num

2.15 生命周期

2.15.1 挂载阶段

2.15.2 更新阶段

2.15.3 卸载阶段

2.15.4 卸载组件(想React.render()一样)

image.png

声明周期(新)

b22a9339-6444-456c-bad0-ac8a84569f05.png

import React, { Component } from 'react'
// 父子组件
class Sub extends Component {
  UNSAFE_componentWillReceiveProps(props) {
    console.log('0-props-更改props') // 第一次传的的不会走这里
  }
  shouldComponentUpdate(nextprops) {
    console.log('1-props-判断是否更改props')
    return nextprops.msg !== this.props.msg
  }
  UNSAFE_componentWillUpdate() {
    console.log('2-props-即将更改props')
  }
  componentDidUpdate() {
    console.log('4-props-完成更改props')
  }
  render() {
    console.log('3-props-渲染')
    return (
      <h1>{ this.props.msg }</h1>
    )
  }
}

export default class Child extends Component {
  constructor(props) {
    super(props)
    console.log('01-挂载-构造器')
  }
  state = {
    msg: 'lixin'
  }
  get() {
    this.setState({
      msg: 'hello'
    })
  }
  UNSAFE_componentWillMount() {
    console.log('02-挂载-开始渲染,之后开始render渲染')
  }
  componentDidMount() {
    console.log('04-挂载-渲染结束')
  }
  shouldComponentUpdate(nextprops, nextstate) {
    /**
     * 1. 如果返回true,代表更新;否则不更新
     * 2. 如果nextstate等于this.state没有必要更新
     * 3. 会走渲染那一步,但是不更新数据
     */
    console.log('01-state-更新阶段-判断是否更新', nextstate)
    return this.state.msg !== nextstate.msg
  }
  UNSAFE_componentWillUpdate() {
    console.log('02-state-更新阶段-即将准备更新')
  }
  componentDidUpdate() {
    console.log('04-state-更新阶段-更新完成')
  }
  render() {
    console.log('03-挂载+更新-渲染中')
    return (
      <div>
        <Sub msg={this.state.msg} />
        <p>{ this.state.msg }</p>
        <button onClick={ this.get.bind(this) }>更新</button>
      </div>
    )
  }
  componentWillUnmount() {
    console.log('卸载阶段') // 通过ReactDOM.unmountComponentAtNode()触发
  }
}
// 强制更新 不经过setState,不更新任何状态中的数据,不经过shouldComponentUpdate

export default class App extends Component {
    shouldComponentUpdate() {
        console.log("1"); // 不会输出
        return true;
    }
    UNSAFE_componentWillUpdate() {
        console.log("2");
    }
    componentDidUpdate() {
        console.log("4");
    }
    update = () => {
        this.forceUpdate();
    };
    render() {
        return (
            <div className="App">
                <h1>Hello CodeSandbox</h1>
                <button onClick={this.update}>强制更新</button>
            </div>
        );
    }
}

新的生命周期与旧的区别: 少了三个 多了两个

  1. getDerivedStateFromProps()
  • 从props衍生出的状态 必须是static函数且返回null或者object
  • 适用场景:state的值取决于props
  1. getSnapshotBeforeUpdate
  • 返回value、null,值可以作为参数传给componentDidUpdate
// getSnapshotBeforeUpdatean 案例

2.16 redux

案例

2.17 函数式组件以及hook

2.17.1函数式组件特点

    1. 没有生命周期
    1. 没有state
    1. 没有this 所以,使用hook代替state、setState,hook你可以写在判断式下面

2.17.2 useState

const [msg, Setmsg] = useState('11111')

2.17.3 useEffect

因为函数式组件没有生命周期,所以用useEffect代替

    1. 如果useEffect后面没有参数的话,代表componentDidMount和componentDidUpdate
    1. 如果是[],代表componentDidMount
    1. 如果是[num1],代表监听num1这个数值变化
    1. 如果callback有返回值,代表卸载阶段

2.17.4 useContext creatContext

    1. 代替props使用,承接上下文
    1. 使用时 也要定义value
    1. 2种用法 第一种
import { createContext, useContext, useState } from "react";

// 1. 定义context

const numContext = createContext();

function Sub(props) {

    // 3. 接受context

    const { num, index, setNum } = useContext(numContext);

    return (

        <>

            <h2>{index}</h2>

            <h2>{num}</h2>

            <button onClick={() => setNum((num) => num + 1)}>累加</button>

        </>

    );

}
export default function App1() {

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

    return (

        <>

            {/* 2. 定义provider

            当value只有一个参数时,一个{}

            多个参数时,{{}}

            */}

            <numContext.Provider value={{ num, index: 1, setNum }}>

                <Sub />

            </numContext.Provider>

            <h3>{num}</h3>

        </>

    );

}

第二种

import { createContext } from "react";

const numContext = createContext();

function Sub() {

    return (

    // 这里不加return 一旦加了,就报错

        <numContext.Consumer>

            {({ age, name }) => (

                <>

                    <h1>我的姓名是:{name}</h1>

                    <h2>我的年龄是:{age}</h2>

                </>

            )}

        </numContext.Consumer>

    );

}


export default function App2() {

    return (

        <numContext.Provider value={{ age: 22, name: "Zoe" }}>

            <Sub />

        </numContext.Provider>

    );

}

2.18 ref

import { useState } from "react";

export default function App() {

    let [count, setCount] = useState(0);

    const v = 11;

    const handleAdd = function () {

        if (v === 1) {

            console.log(v);

            setCount((count) => count + 1);

        }

    };

    return (

        <div>

            <p>{count}</p>

            <button onClick={handleAdd}>加一</button>

        </div>

    );

}

    1. 类组件
  1. 使用字符串形式(简单,但是不建议使用) 在this中,有一个refs对象,里面是ref的存储值 image.png
export default class App extends Component {
    click = () => {
        console.log(this);
        alert(this.refs.input1.value);
    };
    render() {
        return (
            <div className="App">
            <input ref="input1" placeholder="please input" />&nbsp;
            <button onClick={this.click}>Click</button>
        </div>
        );
    }
}
  1. 回调ref(严格模式下不可用)

image.png

export default class App extends Component {
    click = () => {
        console.log(this);
        alert(this.input1.value);
    };
    render() {
        return (
            <div className="App">
                <input
                    ref={(a) => {
                    console.log(a) // 页面加载时就会打印出来,即<input placeholder="please input"></input>
                    this.input1 = a;
                }}
                    placeholder="please input"
                />&nbsp;
                <button onClick={this.click}>Click</button>
            </div>
        );
    }
}
  • 问题:使用回调ref时,在DOM更新、渲染时会调用两次,一次输出null,一次输出对应节点,
  • 原因:每次更新时,react会重新加载一个内联函数,不知道传的dom节点,所以为null,第二次为真实的dom节点
  • 解决方法:使用class里面的函数
  • 总结:与上面的区分不大,直接使用回调就可以,但是输出两次,面试可能会问
export default class App extends Component {
  state = {
    isHot: true
  };
  click = () => {
    console.log(this);
    alert(this.input1.value);
  };
  changeWeather = () => {
    this.setState({ isHot: !this.state.isHot });
  };
  inputRef =  (a) => {
    console.log(a); // 页面加载时就会打印出来,即<input placeholder="please input"></input>
    this.input1 = a;
  }
  render() {
    return (
      <div className="App">
        <h1>今天天气很{this.state.isHot ? "炎热" : "寒冷"}</h1>
        <button onClick={this.changeWeather}>切换</button>
        <input
          ref={this.inputRef}
          placeholder="please input"
        />
        &nbsp;<button onClick={this.click}>Click</button>
      </div>
    );
  }
}

  1. creactRef 推荐使用

image.png this中会增加input1属性、input1是个对象

export default class App extends Component {
    input1 = createRef();
    click = () => {
        console.log(this);
        alert(this.input1.current.value);
    };
    render() {
        return (
            <div className="App">
                <input ref={this.input1} placeholder="please input" />&nbsp;
                <button onClick={this.click}>Click</button>
            </div>
        );
    }
}

2.19 空便签与fragment

空标签与fragment一样,但是空标签不可以设置key、className等

import { Fragment } from "react";

export default function App() {
    const state = [1, 2, 3, 4];
        return (
            <Fragment>
                {
                    state.map((item, index) => (
                        <Fragment key={index}>
                            <h1>{item}</h1>
                        </Fragment>
                    ))
                }
            </Fragment>
    );
}

2.20 错误边界

  • 只可以用在class 第一种,使用componentDidCatch
// index.js
import ReactDOM from "react-dom";
import Test from "./Test";
import ErrorBoundry from "./ErrorBoundry";

const rootElement = document.getElementById("root");
ReactDOM.render(
    <ErrorBoundry>
        <Test />
    </ErrorBoundry>,
    rootElement
);
// Test.js
import { Component } from "react";

export default class Text extends Component {
    state = {
        value: ""
    };
    render() {
        const value = this.state.value;
        if (/^[a-zA-Z]+$/.test(value) || value === "") {
            return (
                <input type="text" 
                    placeholder="please input something" 
                    value={value} 
                    onChange={this.changeFn.bind(this)} 
                />
            );
        }
    }
    changeFn(e) {
        this.setState({
            value: e.target.value
        });
    }
}
// ErrorBoundry.js
import { Component } from "react";
export default class ErrorBoundry extends Component {
    state = {
        hasError: false
    };
    // 捕获错误
    componentDidCatch(error, errorInfo) {
        this.setState({
            error,
            errorInfo,
            hasError: true
        });
    }
    render() {
        if (this.state.hasError) {
            return <h1>出错了!!!!!!</h1>;
        } else {
            return this.props.children;
        }
    }
}

第二种

// ErrorBoundry.js 其他不变
import { Component } from "react";

export default class ErrorBoundry extends Component {
    constructor(props) {
        super(props);
        this.state = {
            hasError: false
        };
    }
    static getDerivedStateFromError() {
        return {
            hasError: true
        };
    }
    // 捕获错误
    render() {
        if (this.state.hasError) {
            return <h6>出错了!!!!!!</h6>;
        } else {
            return this.props.children;
        }
    }
}

2.21 PureComponent

类似shouldComponentUpdate
局限性:

  • 只可以进行浅比较
// shouldComponentUpdate操作
import { Component } from "react";

class Sub extends Component {
    shouldComponentUpdate(nextProps, nextState) {
        if (nextProps.num === 1) {
            return false;
        }
        return true;
    }
    render() {
        return <h2>{this.props.num}</h2>;
    }
}
export default class App extends Component {
    state = {
        num: -1
    };
    add() {
        this.setState({
            num: this.state.num + 1
        });
    }
    render() {
        return (
            <>
                <Sub num={this.state.num} />
                <button onClick={this.add.bind(this)}>添加</button>
            </>
        );
    }
}
// 使用PureComponent
class Sub extends PureComponent {
    render() {
        return <h2>{this.props.num}</h2>;
    }
}

2.22 高阶组件

  • 高阶组件:函数里面返回组件,封装的思想
  • 高姐函数:函数里面返回函数
import { Component } from "react";

// 1- 定义高阶函数

// 此函数中,不需要定义高阶组件的名字,是通过参数传递的,第一个参数为组件名字

// 函数名字是大驼峰

function hdcFunc(ComName, name, age) {
    return class extends Component {
        render() {
            return <ComName name={name} age={age} />
        }
    };
}

// 2-定义使用高姐函数的组件
class Sub1 extends Component {
    render() {
        return (
            <>
                <h2>我的名字是: {this.props.name}</h2>
                <h2>我的年龄是: {this.props.age}</h2>
            </>
        );
    }
}
// 定义使用高姐函数的组件
class Sub2 extends Component {
    render() {
        return (
            <>
                <h2>我的名字是: {this.props.name}</h2>
                <h2>我的年龄是: {this.props.age}</h2>
            </>
        );
    }
}
// 3-定义使用在根组件中使用的组件
const MySub1 = hdcFunc(Sub1, "lixin", 22);
const MySub2 = hdcFunc(Sub2, "Zoe", 24);

export default class App extends Component {
    render() {
        return (
            <>
                <MySub1 />
                <MySub2 />
            </>
        );
    }
}

2.23 懒加载 (LazyLoad + Suspense)

// Child.js
import { Component } from "react";

export default class Child extends Component {
    render() {
        return <h2>加载完毕</h2>;
    }
}
// App.js
import React, { Component, Suspense } from "react";

const Child = React.lazy(() => import("./Child"));
export default class App extends Component {
    render() {
        return (
            <div>
                <Suspense fallback={<h2>loading....</h2>}>
                    <Child />
                </Suspense>
            </div>
        );
    }
}

2.24 context

2.24.1 定义

在单层父子通信中使用props,多层可以用context

2.24.2 使用方法

    1. 先定义context const UserContext = React.createContext('dabaixin')
    1. 定义context传输方 使用context.Provider,定义传输值
function App(){
    return(
        <UserContext.Provider value=“11111”>
            <Slider/> /*中间组件*/
        </UserContet.Provider>
    )
    
}
funciton Slider(){
    return <Info/>
}
    1. 定义接收方,两种方式
  • 3.1 函数式组件 使用consumer
function Info() {
    return(
        <UserContext.Consumer>
            {
                value => (
                    <button>{value}</button>
                ) 
            }
        </UserContext.Consumer>
    )
}
  • 3.2 class组件 必须加static contextType = UserContext;
class Info extends React.Component {
    static contextType = UserContext;
    render() {
        return <button>{this.context}</button>;
    }
}

2.26 包含与继承

2.26.1 包含

  1. 使用props.children
function Test(props) {
    return 
        <div>
            <h1>这是组合</h1>
            {props.children}
        </div>;
}

export default function App() {
    return (
        <div className="App">
            <Test>
                <h1>Hello CodeSandbox</h1>
                <h2>Start editing to see some magic happen!</h2>
            </Test>
        </div>
    );
}

2.26.2 继承

没有用到过

2.27 JSX进阶

<div>
  {props.messages.length  &&    <MessageList messages={props.messages} />
  }
</div>

像上例,如果messages为空数组,React也会渲染.因为,0时falsy值,会被渲染.可以写成以下格式

<div>
  {props.messages.length > 0 &&    <MessageList messages={props.messages} />
  }
</div>

2.28 Portals

  1. 定义
  • 使用React.creatPortal(child, element) 可以把child子组件挂载到element上面
  1. 例子
// These two containers are siblings in the DOM
const appRoot = document.getElementById('app-root');
const modalRoot = document.getElementById('modal-root');

class Modal extends React.Component {
  constructor(props) {
    super(props);
    this.el = document.createElement('div');
  }
  componentDidMount() {
    modalRoot.appendChild(this.el);
  }
  componentWillUnmount() {
    modalRoot.removeChild(this.el);
  }
  render() {
    return ReactDOM.createPortal(this.props.children, this.el);
  }
}

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {showModal: false};
    this.handleShow = this.handleShow.bind(this);
    this.handleHide = this.handleHide.bind(this);
  }
  handleShow() {
    this.setState({showModal: true});
  }
  handleHide() {
    this.setState({showModal: false});
  }
  render() {
    const modal = this.state.showModal ? (
      <Modal>
        <div className="modal">
          This is being rendered inside the #modal-container div.
          <button onClick={this.handleHide}>Hide modal</button>
        </div>
      </Modal>
    ) : null;
    return (
      <div className="app">
        This div has overflow: hidden.
        <button onClick={this.handleShow}>Show modal</button>
        {modal}
      </div>
    );
  }
}
ReactDOM.render(<App />, appRoot);

2.29 Profiler

可以看到渲染一个组件的时间 id等

import { Fragment, Profiler } from "react";
import "./styles.css";

function Modal(props) {
    return (
        <div id="modal">
            <h1>This is modal</h1>
            {props.cihldren}
        </div>
    );
}

export default function App() {
    function onRenderCallback(
        id, // 发生提交的 Profiler 树的 “id”
        phase, // "mount" (如果组件树刚加载) 或者 "update" (如果它重渲染了)之一
        actualDuration, // 本次更新 committed 花费的渲染时间
        baseDuration, // 估计不使用 memoization 的情况下渲染整颗子树需要的时间
        startTime, // 本次更新中 React 开始渲染的时间
        commitTime, // 本次更新中 React committed 的时间
        interactions // 属于本次更新的 interactions 的集合
    ) {
        // 合计或记录渲染时间。。。
        console.log(
            id,
            phase,
            actualDuration,
            baseDuration,
            startTime,
            commitTime,
            interactions
        );
    }

    return (
        <Fragment id="app-element">
            <Profiler id="demo" onRender={onRenderCallback}>
                <Modal>
                    <p>fhgweoihfo</p>
                </Modal>
            </Profiler>
        </Fragment>
    );
}
// 输出结果
demo mount 1.8000000715255737 0.5 671.6000000238419 674.1000000238419 Set {}

2.30 受控组件和非受控组件

  • 非受控组件: 对于值而言,现用现取
  • 受控组件: 把value放在state里面,优点:减少ref的使用

2.31 函数柯里化

  1. 事件回调不可以写(),否则返回的是值,但是可以利用闭包的想法
  2. 高阶函数

2.32 diff算法

diff算法最重要的是key的使用

  • 常见面试题
  • (1)react/vue 中的key有什么作用?(key的原理是什么?)
  • (2)为什么遍历列表时,key最好不要用index 答:key是虚拟DOM对象的标识,在更新显示时起着重要的作用。当数据发生变化时,react会根据新数据渲染出新的虚拟DOM,同时与旧的虚拟DOM进行比较,如果key不同时,会渲染新的真实DOM;不同则比较,若内容不同时,则渲染出新的真实DOM。 使用index作为key时,可能会发生以下场景:
  1. 当数据发生逆序添加、逆序删除等破坏顺序的行为时
  • 会产生没必要的真实DOM的更新 ---> 效率降低
  1. 如果结构中包含输入类的DOM
  • 会产生错误的DOM更新 ---> 界面有问题

image.png
问题:

image.png

三 构建react项目

  1. 安装scss
npm install sass-loader node-sass --save-dev
npm install --save-dev sass-resources-loader
// 在package.json同级文件下新建config-overrides.js
const { override, adjustStyleLoaders } = require("customize-cra");
module.exports = override(
  // ...其他配置...
  adjustStyleLoaders(rule => {
    if (rule.test.toString().includes("scss")) {
      rule.use.push({
        loader: require.resolve("sass-resources-loader"),
        options: {
          resources: "./src/assets/scss/output.scss" 
          //这里是你自己放公共scss变量的路径
        }
      });
    }
  })
);
npm run start即可

使用index.modules.css

  1. 使用绝对路径@
    1. 添加git add . 和git commit
    1. 安装npm run eject
    1. 在config文件夹下,webpack.config.js,
 alias: {
    'react-native': 'react-native-web',
    // Allows for better profiling with ReactDevTools
    ...(isEnvProductionProfile && {
      'react-dom$': 'react-dom/profiling',
      'scheduler/tracing': 'scheduler/tracing-profiling',
    }),
    ...(modules.webpackAliases || {}),
    '@': path.resolve('src'), //添加该语句
  },

图片使用绝对路径

import imgSrc from '@/assets/guide.jpg'
<img src={imgSrc} alt="引导页面图片" />

项目git地址,请多指教,谢谢!
gitee.com/bigwhitexin…