react回顾

89 阅读7分钟

为什么react选择使用jsx(JavaScript Extension)

react认为渲染逻辑和ui处理逻辑存在高度的耦合,比如页面的点击事件需要绑定js,数据的动态处理和更新需要js处理。所以选择使用jsx,认为渲染逻辑和ui处理逻辑两者是密不可分的。在js中处理渲染和ui相关的东西。

jsx只能有一个根元素

因为jsx是交给babel的jsx函数处理的时候,是以一种树状的结构来进行处理转换成html代码,树只能有一个根节点。

jsx中可以显示的值

数组、字符串、数字可以直接插入显示,但是当值的类型为null undefind boolean的时候 jsx默认显示为空,也就是不显示出来。对象类型无法直接显示,可以显示js右值表达式(除开对象)。

事件参数传递,传递两三个参数包括默认参数

使用箭头函数,或者利用bind(),但是bind所传递的event是在参数列表的最后

jsx的本质

jsx 仅仅只是 React.createElement()的语法糖,jsx经过babel中的_jsx函数经过转换后,供React.createElement()函数使用(类似于vue中的h函数),来生成虚拟dom(vdom),最终可以运行在各个平台。

// jsx代码
<div>
  <button onClick={ () => {this.setState({ isShow: !isShow })} }>切换</button>
  { isShow && <h2>react</h2> }
  <h2 style={{display: isShow ? "block" : 'none'}}>react2</h2>
</div>

经过babel转换后的代码

import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
/*#__PURE__*/_jsxs("div", {
  children: [/*#__PURE__*/_jsx("button", {
    onClick: () => {
      this.setState({
        isShow: !isShow
      });
    },
    children: "\u5207\u6362"
  }), isShow && /*#__PURE__*/_jsx("h2", {
    children: "react"
  }), /*#__PURE__*/_jsx("h2", {
    style: {
      display: isShow ? "block" : 'none'
    },
    children: "react2"
  })]
});

组件划分

react中类组件、函数式组件、有状态组件、无状态组件、展示型组件以及容器型组件。

  1. 类组件:
  • 组件的名称必须是大写字母开头(无论是类组件还是函数式组件)
  • 类组件必须继承自React.Component
  • 类组件必须实现render(),render函数可返回:react元素、数组、数字、布尔类型、portal、fragment、null以及undefined。
  1. 函数式组件:
  • 通过函数所创建的组件,不需要继承任何东西。
  • 函数的返回值必须要与类组件中的render()返回值一样。
  1. 函数式组件与类组件的区别:
  • 函数式组件没有声明周期函数,通过hooks来操作。

  • 函数式组件this不能指向组件实例。

  • 函数式组件没有内部的state,只能通过hooks来操作。

  1. 类组件生命周期:

image.png 先是类的构造函数

父子通信

  1. 类组件的父子通信,父传子通过构造函数的props获取所传来的值。对传入的类型进行限制,通过propTypes进行限制类型。需要引入 propTypes
MainBanner.propTypes = {
  banner: propTypes.array.isRequired,
  title: propTypes.string
}

通过defaultProps 设置默认值,es2022 可以通过static在类中设置 defaultProps

MainBanner.defaultProps = {
  banner: [],
  title: "默认"
}
  1. 子传父通信,通过props传递过去的函数进行子传父的通信。 案例:
import {Component} from 'react';
import "./index.css";
export class TabControl extends Component {
  constructor(p) {
    super(p);
    this.state = {
      currentIndex: 0
    }
  }

  handleClick(currentIndex) {
    this.setState({
      currentIndex
    })
    this.props.tabClick(currentIndex);
  }

  cb = (item, index) => {
    const { currentIndex } = this.state;
    return (
        <div key={index} onClick={() => this.handleClick(index)} className={ this.getClassName(
            currentIndex, index)}>
          { item }
        </div>
    )
  }

  getClassName(currentIndex, index) {
    return `item ${currentIndex === index ? 'active' : ''}`;
  }

  render() {
    const { titles } = this.props;
    return (
        <div>
          <div className="wrap">
            { titles.map(this.cb) }
          </div>
        </div>
    );
  }
}
import {Component} from 'react';
import {TabControl} from './TabControl';

export class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      tabIndex: 0,
      titles: ['aa', 'bb', 'cc']
    }
  }

  changeTab(tabIndex) {
    this.setState({
      tabIndex
    })
  }

  render() {
    const { titles, tabIndex } = this.state;
    return (
        <div>
          <TabControl tabClick={(i) => this.changeTab(i)} titles={ titles }/>
          <h2>{ titles[tabIndex] }</h2>
        </div>
    );
  }
}

插槽

在react中实现插槽的两种方式

  • 组件的children子元素
import React, {Component} from 'react';
import NaviBar from './nav-bar/NaviBar';

class App extends Component {
  render() {
    return (
        <div>
          <NaviBar>
            <button>left</button>
            <button>center</button>
            <button>right</button>
          </NaviBar>
        </div>
    );
  }
}

export default App;
import React, {Component} from 'react';
import "./style.css"

class NaviBar extends Component {
  render() {
    const { children } = this.props
    return (
        <div className="content">
          <div className="left">{ children[0] }</div>
          <div className="center">{ children[1] }</div>
          <div className="right">{ children[2] }</div>
        </div>
    );
  }
}

NaviBar.propTypes = {

};


export default NaviBar;
  • props属性传递React元素
import React, {Component} from 'react';
import "../nav-bar/style.css"

class NavBarTwo extends Component {
  render() {
    const { left, center, right } = this.props
    return (
        <div className="content">
          <div className="left">{ left }</div>
          <div className="center">{ center }</div>
          <div className="right">{ right }</div>
        </div>
    );
  }
}

export default NavBarTwo;
import React, {Component} from 'react';
import NaviBar from './nav-bar/NaviBar';
import NavBarTwo from './nav-bar-two/NavBarTwo';

class App extends Component {
  render() {
    return (
        <div>
          <NaviBar>
            <button>left</button>
            <button>center</button>
            <button>right</button>
          </NaviBar>
          <NavBarTwo left={<h1>left</h1>} center={<h1>center</h1>} right={<h1>right</h1>}/>
        </div>
    );
  }
}

export default App;
  • 作用域插槽,通过props上传递的回掉函数实现
import {Component} from 'react';
import {TabControl} from './TabControl';

export class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      tabIndex: 0,
      titles: ['aa', 'bb', 'cc']
    }
  }

  changeTab(tabIndex) {
    this.setState({
      tabIndex
    })
  }

  render() {
    const { titles, tabIndex } = this.state;
    return (
        <div>
          <TabControl itemType={(item) => <h1>{item}</h1>} tabClick={(i) => this.changeTab(i)} titles={ titles }/>
          <h2>{ titles[tabIndex] }</h2>
        </div>
    );
  }
}
import {Component} from 'react';
import "./index.css";
export class TabControl extends Component {
  constructor(p) {
    super(p);
    this.state = {
      currentIndex: 0
    }
  }

  handleClick(currentIndex) {
    this.setState({
      currentIndex
    })
    this.props.tabClick(currentIndex);
  }

  cb = (item, index) => {
    const { currentIndex } = this.state;
    const { itemType } = this.props
    return (
        <div key={index} onClick={() => this.handleClick(index)} className={ `item ${currentIndex === index ? 'active' : ''}`}>
          { itemType(item) }
        </div>
    )
  }
  render() {
    const { titles } = this.props;
    return (
        <div>
          <div className="wrap">
            { titles.map(this.cb) }
          </div>
        </div>
    );
  }
}

setState()

为什么要使用setState函数,因为在react中不像vue一样有数据依赖的追踪,各个细节对于react开发者来说都是可见的,所以当数据改变的时候,要通知react该组件有状态发生了改变,所以要有一个函数去操作执行通知,那么该函数就是setState。setState可以传入对象,回掉函数,第二个参数相当于异步后的then。且setState原理是对新对象和原始对象的合并(使用assign方法),When you call setState(), React merges the object you provide into the current state. React18之前该函数为什么设计成异步,作者在github中回答道,设计成异步因为两个原因:

  1. 因为在setState中有render函数,数据更新要渲染UI使得UI上的数据也更新,如果是同步操作 比如说像一个循环中数据被更改了上百次,此时同步更新会产生上百个vdom,去进行diff算法。这是完全损耗性能且没必要的。所以就设计成异步,当一定时间后执行。
  2. 如果是更改数据同步,会造成父子组件数据不同步刷新,父组件更新了要传递给自组件的数据但是此时没有去执行render那么子组件的数据还是之前的,所以就要将更改数据与render一起执行,render是异步的且异步性能消耗小,所以setState设计成异步。
  3. 但是在react18之前,在settimeout或原声dom操作中的setstate操作是同步操作交给浏览器操作,react18之后的setstate变成了异步操作加入到队列中进行批处理。但是如果还想在react18中同步操作,可以使用flushSync

优化

scu优化,shouldComponentUpdate(nextProp, nextState) 该函数返回布尔值通知componentUpdate是否执行render,可以在该函数中对比两次的状态和所传入的属性,如果没有发生变化手动返回false不进行更新操作,节省性能消耗。在react中state或prop发生改变都会重新渲染。
手动实现太麻烦,在react中可以直接继承purecomponent(纯组件类的继承,进行浅层比较,引用类型比较地址、值类型比较值)、memo(函数式组件的使用)

import React, {Component, PureComponent} from 'react';
import Home from './Home';

class App extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      msg: "a"
    }
  }

  render() {
    console.log("App rerender");
    const { msg } = this.state;
    return (
        <div>
          <button onClick={() => this.setState({ msg: "b" })}>change</button>
          <Home msg={ msg }></Home>
        </div>
    );
  }
}

export default App;
import React, {memo} from 'react';

const Home = memo(function(prop) {
  console.log("Home rerender");
  return (
      <div>
        <h1>{ prop.msg }</h1>
      </div>
  );
})

export default Home;

受控组件与非受控组件

这常见于表单组件中,因为在react中没有双向绑定,只能自己手动来实现双向绑定。所以当表单组件中给value绑定了属于react的state或props,该组件就变成了受控组件,得额外绑定onchangge事件来实现双向绑定。如果使用defaultValue给组件绑定默认值,那么该html标签还是非受控组件。

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

class App extends PureComponent {
  constructor(props) {
    super(props);

    this.inputRef = createRef();
    this.state = {
      username: '',
      pwd: '',
      isAgree: false,
      hobbies: [
        {name: 'a', text: '唱', checked: false},
        {name: 'b', text: '跳', checked: false},
        {name: 'c', text: 'rap', checked: false},
      ],
      fruit: [],
      intro: "saa"
    }
  }

  componentDidMount() {
    this.inputRef.current.addEventListener("input", (e) => { console.log(e); })
  }

  handleOnSubmit(e) {
    e.preventDefault();
    console.log(this.state, this.inputRef.current.value);
  }

  handleInputChange(e) {
    this.setState({
      [e.target.name]: e.target.value
    })
  }

  handelIsAgreeChange(e) {
    this.setState({
      isAgree: e.target.checked
    })
  }

  handleHobbiesChange(e, index) {
    this.state.hobbies[index].checked = e.target.checked;

    this.setState({
      hobbies: [... this.state.hobbies]
    })
  }

  handleFruitChange(e) {
    this.setState({
      fruit: [...e.target.selectedOptions].map(item => item.value)
    })
  }

  render() {
    const { username, pwd, isAgree,hobbies, fruit, intro } = this.state;
    return (
        <form onSubmit={(e) => this.handleOnSubmit(e)}>
          <div>

            <label htmlFor={username}>
              用户
              <input type="text" id='username' name='username' value={username}
                     onChange={(e) => this.handleInputChange(e)}/>
            </label>
            <label htmlFor={pwd}>
              密码
              <input type="text" id='pwd' name='pwd' value={pwd}
                     onChange={(e) => this.handleInputChange(e)}/>
            </label>
          </div>

          {/* 单选 */}
          <div>
            <label htmlFor="isAgree">
              <input type="checkbox" checked={isAgree} onChange={(e) => this.handelIsAgreeChange(e)}/>
              是否同意
            </label>
          </div>

          {/* 多选 */}
          <div>
            <label htmlFor="hobbies">
              {
                hobbies.map((item, index) => <input key={item.name} type="checkbox" name={item.name} checked={item.checked} onChange={(e) => this.handleHobbiesChange(e, index)}/>)
              }
            </label>
          </div>

          <select value={fruit} onChange={(e) => this.handleFruitChange(e)} multiple>
            <option value="apple">苹果</option>
            <option value="banana">香蕉</option>
          </select>

          <div>
            <input type="text" defaultValue={intro} ref={this.inputRef}/>
          </div>

          <div>
            <button type="submit">提交</button>
          </div>
        </form>
    );
  }
}

export default App;

高阶组件

高阶组件是参数为组件,返回值为新组件的函数主要作用对要渲染的组件做拦截和增强。
高阶组件可以优雅的处理react代码,可以实现mixin混入,但是hoc嵌套过多也会造成调试困难。

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

function enhanced(C) {
class Com extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      name: "canoe",
      age: 18
    }
  }

  render() {
    return <C {...this.state} {...this.props}/>
  }
}
return Com
}

const Home = enhanced((props) => {
return <h1>Home: Name{props.name} Age{props.age} {props.msg}</h1>
})

class App extends Component {
render() {
  return (
      <div>
        <Home msg={"hello"}/>
      </div>
  );
}
}

export default App;

Portal

某些情况下,我们希望渲染的内容独立于父组件,甚至是独立于当前挂载到的DOM元素中(默认都是挂载到id为root的dom元素中)

fragment

在render返回的元素中,都需要一个根的div,如果不需要根div,可以使用fragment进行包裹。简写语法为<></>,如果要添加key不能省略。vue3中不需要根div,源码中也用fragment进行了处理。

css使用

  1. 内联样式,优点:能隔离、能使用js变量。缺点:无法编写伪元素、伪类等。
  2. 普通css样式,此种方式为全局的css,容易产生冲突。
  3. cssmodule,react所推荐的。需要在webpack配置环境中配置,modules:true。react脚手架已经配置好了。
  4. css in js,css in js是一种模式,其中css是由js生成而不是在外部文件中定义。由第三方库提供。目前流行的库有styled-comp emotion glamorous。css-in-js通过js来为css赋予一些能力,包括类似于css预处理器一样的样式嵌套、函数定义、逻辑复用、动态修改状态等等。虽然css预处理器也具有某些能力,但是动态状态依然是一个不好处理的点。所以,目前可以说css-in-js是react编写css最为受欢迎的一种解决方案。
import React, {Component} from 'react';
import AppWrapper from './style';
class App extends Component {
  render() {
    return (
        <AppWrapper>
          <div className="section">
            <h2 className="title">title</h2>
            <p className="content">content</p>
          </div>

          <div className="footer">
            <p>pronounce</p>
            <p>pronounce</p>
          </div>
        </AppWrapper>
    );
  }
}

export default App;
import styled from 'styled-components';

const AppWrapper = styled.div`
  .section {
      border: 1px solid red;
      
      &:hover {
          background-color: skyblue;
      }
  }
`

export default AppWrapper

想跟vue一样动态绑定class处理,可以使用classnames库。