React 复习

React的三个核心依赖库

  <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
  <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
  <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

使用React需要引入上述的三个库,crossorigin这个属性是可以获取到跨站脚本的错误信息。

关于React为何要分为三个库主要是因为react-native因为手机端的react-dom会将React渲染出来的虚拟对应到原生的控件上,在web端就是生成真实的dom

为什么要引入babel

babel在前端,用途是非常的广泛。作为前端经常使用的代码转换器和编译器。可以对js做兼容性处理,比如es6es5

默认React的开发可以不需要babel,但是我们要写一些html模板就需要使用React.createElement这个函数,但是要先一些嵌套的额标签的话,那是十分繁琐的。所以我们可以编写jsx代码(JavaScript XML),babel的作用就是将jsx语法转换为React.createElement写法。
比如:

image-20210917193630852
左边我们可以按照html的写法创建标签,而经过babel编译后最终为React.createElement形式,可以看到该形式是较为复杂。

JSX语法

我们要编写jsx语法需要将代码写在如下标签中,表示我们写的为jsx代码。

  <script type="text/babel">
    let message = 'hello saber';
    function btnClick() {
      message = 'hello react';
      render();
    }

    function render() {
      const VDOM = (
        <div>
          <div>{message}</div>
          <button onClick={btnClick}>改变文本</button>
          <div>
            <span>你</span>
            <span>好</span>
          </div>
        </div>

      )
      ReactDOM.render(VDOM, document.getElementById('test'))
    }
    render();
  </script>

jsx的书写规范

  1. 首先jsx顶层只能有一个根元素,我们需要在外层包裹一个div,或者Fragment标签。
  2. 如果我们要换行书写还需要在外层包裹一个()
  3. jsx支持单标签但是要以 /> 结尾。
  4. jsx内部要混入变量使用{},其中也可以使用一些表达式。
  5. 标签的属性如果与一些保留词冲突的话需要改写,比如样式的类名class需要使用className,另外还有label标签中的for属性,要使用htmlFor来代替。
  6. 内联样式,要用style={{key:value}}的形式去写。

{}中能展示的变量类型

可以展示的类型:string, number,Array(会将每一项拼在一起成字符串)。

不能展示的类型:undefined,null,boolean,一般对象不能不能作为jsx的子类即对象不能直接放置在{}中。

{}不能展示的变量类型,如果需要展示的话可以转换变量类型或者调用toString()等方法。

条件渲染

当逻辑判断很多的时候可以使用if else来判断,简单的情况可以使用三目运算符或者交集运算符。
还可以根据性能要求使用display:none或者操作dom的方式来切换dom显示。

  <script type="text/babel">
    class App extends React.Component {
      constructor(props) {
        super(props);

        this.state = {
          isLogin: true
        }
      }

      render() {
        const { isLogin } = this.state;

        // 1.方案一:通过if判断: 逻辑代码非常多的情况
        let welcome = null;
        let btnText = null;
        if (isLogin) {
          welcome = <h2>欢迎回来~</h2>
          btnText = "退出";
        } else {
          welcome = <h2>请先登录~</h2>
          btnText = "登录";
        }

        return (
          <div>
            <div>我是div元素</div>

            {welcome}
            {/* 2.方案二: 三元运算符 */}
            <button onClick={e => this.loginClick()}>{isLogin ? "退出" : "登录"}</button>

            <hr />

            <h2>{isLogin ? "你好啊, coderwhy": null}</h2>

            {/* 3.方案三: 逻辑与&& */}
            {/* 逻辑与: 一个条件不成立, 后面的条件都不会进行判断了 */}
            <h2>{ isLogin && "你好啊, coderwhy" }</h2>
            { isLogin && <h2>你好啊, coderwhy</h2> }
          </div>
        )
      }
      loginClick() {
        this.setState({
          isLogin: !this.state.isLogin
        })
      }
    }
    ReactDOM.render(<App />, document.getElementById("app"));
  </script>

事件绑定和this

jsx中的dom事件绑定就是在指定的dom上以onXxxx形式绑定事件

<h2 onClick={this.btnClick}>点击</h2>

关于绑定事件函数中的this指向问题。上述函数中的this不做任何处理时默认指向undefined的。

因为React在处理绑定的事件函数内部通过 btnClick.apply(undefined,[arg])来调用的,内部是无法获得该上下文的this的。

解决方式1:显示绑定this

this.btnClick.bind(this);

解决方式2:使用箭头函数来定义事件函数

btnClick = () => {
    console.log(this)
}

解决方式3:在{}是用箭头函数返回事件函数

<button onClick={() => { this.decrement("why") }}>-1</button>

推荐使用第三种,因为传值的时候也不需要使用柯里化来定义事件函数了。

jsx本质

jsx实际上就是React.createElement的语法糖

通过createElement创建出来的对象就是ReactElement对象,最终形成一个虚拟DOM

react渲染流程:

jsx -> createElement函数 -> ReactElement(对象树) -> ReactDOM.render -> 真实的DOM

react native渲染流程:

jsx -> createElement函数 -> ReactElement(对象树) -> ReactDOM.render -> 原生的控件(UIButton/Button)

源码展示:

export function createElement(type, config, children) {
  let propName;

  // Reserved names are extracted
  const props = {};

  let key = null;
  let ref = null;
  let self = null;
  let source = null;

  if (config != null) {
    if (hasValidRef(config)) {
      ref = config.ref;

      if (__DEV__) {
        warnIfStringRefCannotBeAutoConverted(config);
      }
    }
    if (hasValidKey(config)) {
      key = '' + config.key;
    }

    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    // Remaining properties are added to a new props object
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }

  // Children can be more than one argument, and those are transferred onto
  // the newly allocated props object.
    //多余的参数都会放到一个数组中来处理
  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
      //开辟一个数组,使得插入更高效
    const childArray = Array(childrenLength);
      //遍历添加
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    if (__DEV__) {
      if (Object.freeze) {
        Object.freeze(childArray);
      }
    }
    props.children = childArray;
  }

  // Resolve default props
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  if (__DEV__) {
    if (key || ref) {
      const displayName =
        typeof type === 'function'
          ? type.displayName || type.name || 'Unknown'
          : type;
      if (key) {
        defineKeyPropWarningGetter(props, displayName);
      }
      if (ref) {
        defineRefPropWarningGetter(props, displayName);
      }
    }
  }
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

React的组件化

创建组件方式

React有两种创建组件的方式:类式组件和函数式组件。
另外根据组件的其他概念,比如状态,职责,UI等,可分为:有状态组件,无状态组件,展示型组件和容器型组件,异步组件,高阶组件等。

类式组件

  1. 首先无论是类组件还是函数式组件,创建时的名称首字母必须大写,不然会被识别为html的内置标签。
  2. 类组件实现需要继承React.component或者React.pureComponent这个父类组件。
  3. 类组件必须实现render函数,该函数也是唯一必须实现的。

官网现在推荐使用ES6的类定义语法来定义类式组件。

import React, { Component } from 'react'
export default class componentName extends Component {
  render() {
    return (
      <div>  
      </div>
    )
  }
}

函数式组件

函数式组件就是使用function函数表达式来定义组件,需要返回的内容结构和类组件中render函数返回的结构相同。

相比类式组件,函数式组件有以下特点:

  • 没有生命周期,类式组件有一套自己的生命周期函数。
  • 没有this实例,类式组件的this指向实例。
  • 没有内部的状态(state),类式组件可以通过在this.state中定义状态变量。
export default function ComponentName(){
    return (
      <div></div>
    )
}

生命周期函数

React组件的生命周期函数表示了组件从开始创建到渲染成功,到最后销毁这一系列的过程。我们可以通过这些生命周期函数来在不同的组件阶段实现各种功能。

函数式组件可以通过hooks来模拟生命周期函数,这里我们主要谈类式组件的生命周期函数。

constructor

谈之前我们先谈下类式组件的构造函数constructor

我们使用constructor一般用来初始化组件的state,或者给事件绑定this,或者父类的构造函数来保存props

constructor(props){
    super(props);
    this.state = {message:'hello world'}
}

上述的构造函数可以省去不写,因为默认不写时,React在执行生成实例时就会调用上述实现方式的构造函数。

如果不写构造函数,我们也可以将state直接写在类式作用域中,调用方式不变:

   class MyComponent extends React.Component {
    //初始化状态
    state = {isHight:false}
     render() {
       const {isHight} = this.state;
       return <h2>你{isHight ? '身体一定很好' : '是个LSP'}</h2>
     }
   }

但是还有一个属性props,如果我们没有在构造函数中声明保存,这个属性我们也可以直接在render获取。

export default class index extends Component {
  // constructor(props) {
  //   super(props);
  // }
  render() {
    const { name, age } = this.props;
    return (
      <div>
        我是home组件
        <span>
          <br />
          {name}---{age}
        </span>
      </div>
    );
  }
}

props是获取从父组件传递来的参数的属性,之所以render函数能够获取到props,是内部源码做了一层处理,将props保存了下来。所以我们可以直接使用props

生命周期函数在React的不同版本有所不同,本篇所讲的主要针对于React版本大于16.13.1

旧版生命周期函数图解:
2_react生命周期(旧)

新版生命周期函数图解:
3_react生命周期(新)

旧版的生命周期函数已经不推荐使用了,当然仍然可以使用。如果要使用的话需要在函数名前加上UNSAFE_前缀。我们不加前缀的话React会内部转发调用带前缀的生命周期函数,同时会报警告。
以下生命周期是过时的的:

  • UNSAFE_componentWillMount() 组件即将挂载成功。
  • UNSAFE_componentWillReceiveProps() 在已挂载的组件接收新的 props 之前被调用。
  • UNSAFE_componentWillUpdate() 当组件收到新的 props state 时。会在渲染之前调用该函数。

新版生命周期相对要复杂些:

componentDidMount

当我们的组件第一次挂载完毕(插入DOM树)后,就会执行该函数。我们可以在这个生命周期函数中执行一些初始化的事,比如:一些需要依赖DOM的操作(获取一些DOM元素属性),网络请求,一些事件的订阅(对应在componentWillUnmount中取消订阅)。

componentDidUpdate

当组件更新完后会被立即执行,首次渲染不会执行该方法。一般我们是通过this.setState或者render函数调用来触发这个函数。该函数可以获取上一次的statepropssnapshot(快照值)。可以比较是否改变来执行相应的操作。注意我们可以在该函数中调用this.setState方法,但是要注意需要条件判断执行,否则会出现死循环。

componentWillUnmount

组件即将卸载的函数,可以在该函数中执行取消订阅,网络请求取消或者引用销毁等操作。我们可以手动调用ReactDOM.unmountComponentAtNode()来卸载指定的节点

getDerivedStateFromProps

这个函数并不常用,他是定义在类上的静态方法需要加上类名或者static。当state的值在任何时候都是取决于props就可以使用该函数。该方法返回一个对象来更新state

getSnapshotBeforeUpdate

在组件即将更改前执行,可以获取一些DOM信息,然后返回。我们可以通过componentDidUpdate这个函数来接受返回值。

shouldComponentUpdate

React组件会根据这个函数返回的值来确定是否更新,默认的行为是state每次发生变化组件都会更新。
propsstate 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。返回值默认为 true。这里注意如果调用forceUpdate强制更新,会跳过该函数执行。关于该函数的其他知悉可以了解:[React性能优化]()

总结:

  1. 初始化阶段,由ReactDOM.render()触发---初次渲染

    1. constructor()
    2. static getDerivedStateFromProps()
    3. render()
    4. componentDidMount()
  2. 更新阶段,由组件内部this.setSate()或父组件重新render触发

    1. static getDerivedStateFromProps()
    2. shouldComponentUpdate()
    3. render()
    4. getSnapshotBeforeUpdate
    5. componentDidUpdate()
  3. 卸载阶段:由内部ReactDOM.unmountComponentAtNode()触发

    1. componentWillUnmount

组件的通信方式

在组件中我们会经常遇到组件相互传值的方式,其中需要用到props这个重要的属性。

父组件传递给子组件

父组件通过在子组件标签上以 属性=值 方式传递数据给子组件。

父组件:

export default class SpuerCon extends Component {
    render(){
        return (
          <ChildrenCon name='zhangsan' age={18} />
        )
    }
}

类子组件接收:

export default class ChildrenCon extends Component {
    render(){
        return (
          <h2>
            {this.props.name}---{this.props.age}
          </h2>
        )
    }
}

函数组件接收:

export function ChildrenCon(props){
    return (
    <h2>{props.name}---{props.age}</h2>
    )
}

如果使用过Vue,我们给子组件传递值的时候,子组件可以对传递过来的props进行验证。React也可以对传递过来的props进行验证,但是React自身并不能验证,我们需要借助第三方库或者TS来进行验证。这里第三方库推荐prop-types

//npm安装方式
npm install prop-types -S
//yarn 安装方式
yarn add prop-types

使用方式:

import React, { Component } from "react";
import PropTypes from 'prop-types';

export default class ChildrenCon extends Component {
    render(){
        const {name, age} = this.props;
        return (
        <h2>{name}---{age}</h2>
        )
    }
    
    //类型验证
    static propTypes = {
        name:PropTypes.string.isRequired
    }
    //默认值设置
    static defaultProps = {
        age:100
   }
}

子组件传递给父组件

Vue中子传递父是通过自定义事件实现,在React中就还是通过Props实现的,我们可以向子组件传递一个函数,让子组件执行来实现子传父。

import React, { Component } from "react";

//子组件
class Btn extends Component {
  render() {
    return <button onClick={this.props.increment}>+</button>;
  }
}

//父组件
export default class CpnBtn extends Component {
  constructor(props) {
    super(props);
    this.state = {
      counter: 0,
    };
  }
  render() {
    return (
      <div>
        <div>{this.state.counter}</div>
        <Btn increment={(e) => this.increment()} />
      </div>
    );
  }
  increment() {
    this.setState({
      counter: this.state.counter + 1,
    });
  }
}

非父子组件数据的共享

有时候我们需要跨多个层级进行数据共享,但是一直通过props逐层传递就会出现中间的冗余。

我们可以使用ReactContext方法。

Context是为了共享那些对于一个组件树而言是"全局"的数据,例如当前认证的用户、主题或首选语言等。
我们首先需要创建一个共享的Context对象,我们可以在createContext()函数中传入一个对象设置默认值。

//创建context对象,类组件获取context的方式
const ProfileContext = React.createContext();
const ThemeContext = React.createContext();

当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效。此默认值有助于在不使用 Provider 包装组件的情况下对组件进行测试。注意:将 undefined 传递给 Provider value 时,消费组件的 defaultValue 不会生效。

export default class Con extends Component {
  constructor(props) {
    super(props);
    this.state = {
      nickname: "zhangsan",
      level: 1991,
    };
  }
  render() {
    return (
      <div>
        <ProfileContext.Provider value={this.state}>
          <ThemeContext.Provider value={{ color: "red" }}>
            <Profile />
          </ThemeContext.Provider>
        </ProfileContext.Provider>
      </div>
    );
  }
}

function Profile(props) {
  return (
    <div>
      {/*  <ProfileHeader /> */}
      <FProfileHeader />
      <ul>
        <li>设置1</li>
        <li>设置2</li>
        <li>设置3</li>
        <li>设置4</li>
      </ul>
    </div>
  );
}

后代元素接收数据,类和函数组件都可以通过Consumer方式获取。

function FProfileHeader() {
  return (
    <div>
      <ProfileContext.Consumer>
        {(value) => {
          return (
            <div>
              <ThemeContext.Consumer>
                {(theme) => {
                  return (
                    <div>
                      <h2 style={{ color: theme.color }}>
                        用户等级:{value.nickname}
                      </h2>
                      <h2>用户昵称:{value.level}</h2>
                    </div>
                  );
                }}
              </ThemeContext.Consumer>
            </div>
          );
        }}
      </ProfileContext.Consumer>
    </div>
  );
}

另一种获取数据方式,只有类组件可以使用:

class ProfileHeader extends Component {
  //声明接收context后就可以使用this.context来获取了
  static contextType = ProfileContext;
  render() {
    return (
      <div>
        <h2>用户等级:{this.context.level}</h2>
        <h2>用户昵称:{this.context.nickname}</h2>
      </div>
    );
  }
}

事件总线

事件总线其实就是利用发布订阅方式进行跨组件传递,这里推荐两个库:eventspubsub-js,这两个库都可以用作传递数据,这里就以events来做案例。

安装方式:

//npm方式
npm install events -S
//yarn方式
yarn add events

使用方式:

import React, { PureComponent } from "react";
//引入
import { EventEmitter } from "events";
//创建事件
const event = new EventEmitter();
export default class Home extends PureComponent {
  render() {
    return (
      <div>
        我是home组件
        <Profile />
      </div>
    );
  }
  //一般在这个函数中执行事件监听
  componentDidMount(){
    event.on('test1', (arg)=>{
      console.log(arg);
    })
  }
  //取消监听
  componentWillUnmount(){
    event.removeListener('testt1');
  }
}
class Profile extends PureComponent {
  render() {
    return <div onClick={e=>this.send()}>我是profile组件</div>;
  }
  send(){
    event.emit('test1', {name:12, age:33});
  }
}

React中实现vue的slot

React中没有像Vue中提供的slot插槽相关的API,但是灵活的JSX属性可以实现类似的功能。
方式1:

import Nav from "./nav";
function App() {
  return (
    <div className="App">
      <Nav>
        <div>left</div>
        <div>center</div>
        <div>right</div>
      </Nav>
    </div>
  );
}
export default App;

卸载组件中的标签会以props的形式传递给组件,组件中获取方式如下:

import React, { Component } from "react";
import "./nav.css";
export default class componentName extends Component {
  render() {
    const { children } = this.props;
    return (
      <div className="nav">
        <div className="nav-left">{children[0]}</div>
        <div className="nav-center"> {children[1]}</div>
        <div className="nav-right"> {children[2]}</div>
      </div>
    );
  }
}

这种方式可以实现slot的效果,但是还是不够灵活,组件内必须按照固定的位置来渲染组件。

方式2:

import Nav2 from "./nav2";

function App() {
  /* 两种方式传递组件,类似slot */
  return (
    <div className="App">
      <Nav2
        leftSlot={<div>left</div>}
        centerSlot={<div>center</div>}
        /* href如果只写#可能会触发eslint,要避免出发警告可以加上个 / */
        rightSlot={<a href="/#">right</a>}
      />
    </div>
  );
}
export default App;

通过属性传值方式就可以在外部控制指定位置的插槽了,组件中接收方式如下:

import React, { Component } from "react";

import "./nav.css";
export default class componentName extends Component {
  render() {
    const { leftSlot, centerSlot, rightSlot } = this.props;
    return (
      <div className="nav">
        <div className="nav-left">{leftSlot}</div>
        <div className="nav-center"> {rightSlot}</div>
        <div className="nav-right"> {centerSlot}</div>
      </div>
    );
  }
}

这种方式在外部就可以控制插槽的位置,类似于vue中的具名插槽。

PureComponent

当我们在state中添加一个属性,但是我们并没有在jsx中使用这个属性,即视图并没有依赖这个数据,我们就不用更新视图,但是React只要监听到state的改变就会重新调用render。另外如果子元素也没有使用父元素的state那么也不用更新,但是只要父元素执行了render函数,其子元素的所有的render函数都会执行,这些更新是完全没有必要。我们可以通过shouldComponentUpdate来控制组件的更新。

该函数默认返回true,即需要调用render函数,我们可以比较新旧props来判断是否需要更新,即返回false就不更新。当然React是想到了这个问题,所以提供了一个组件PureComponent。我们书写组件如果需要判断是否更新就可以继承这个组件

export default class Con extends PureComponent {
    //...
}

该组件重写了shouldComponentUpdate这个方法,默认对新旧的state或者props进行浅比较,判断是否更新。所以我们不需要自己实现比较函数,当然React也不推荐我们自行实现深比较函数,因为深比较也会消耗性能,这是不必要的。

React通过该比较函数判断是否需要更新

function shallowEqual(objA: mixed, objB: mixed): boolean {
  if (is(objA, objB)) {
    return true;
  }

  if (
    typeof objA !== 'object' ||
    objA === null ||
    typeof objB !== 'object' ||
    objB === null
  ) {
    return false;
  }

  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);

  if (keysA.length !== keysB.length) {
    return false;
  }

  // Test for A's keys different from B.
  for (let i = 0; i < keysA.length; i++) {
    if (
      !hasOwnProperty.call(objB, keysA[i]) ||
      !is(objA[keysA[i]], objB[keysA[i]])
    ) {
      return false;
    }
  }

  return true;
}

高阶组件memo

我们之前处理组件更新问题对于类组件时可以使用PureCompoent,但是对于函数式组件我们就可以使用memo这个高阶组件来处理。

我们可以使用这个高阶组件来包裹一个函数式组件返回一个组件。

import React, { PureComponent, memo } from "react";
const Fcn = memo(function () {
  console.log("函数组件重新调用");
  return <div>函数组件</div>;
});

这样该函数就和继承了PureComponent的类式组件一样了。

ref

React的原生开发中也不建议直接操作DOM,但有时我们确实获取DOM可以通过ref方式。

React中使用ref有三种方式:

字符串方式

<h2 ref="dom">你好</h2>
console.log(this.refs.dom);

这种方式已经过时,官方不推荐使用了

对象形式

首先创建一个ref对象

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

export default class Con extends Component {
  constructor(props) {
    super(props);
    this.dom = createRef();
  } 
}
render() {
    return (
      <div>
        <h2 ref={this.dom1}>世界</h2>
      </div>
    );
  }

获取方式:

console.log(this.dom1.current);

函数形式

传入一个回调函数,这个函数会传入一个元素对象,我们定义属性接收就可以。

<h2 ref={(c) => (this.dom2 = c)}>哈哈</h2>
console.log(this.dom2)

推荐方式二三都可以,但是要注意上述方式都只适用于类组件,函数组件需要通过React.forwardRef方式获取。

ref的转发

之前说的方式都只适用于类式组件,而函数时组件可以通过React中自带的一个高阶函数(forwardRef)处理来使用ref

import React, { PureComponent, createRef, forwardRef } from "react";
const Profile = forwardRef(function (props, ref) {
  /* 通过该高阶函数增强,多了一个参数ref,此时将ref放在指定元素上就可以获取对应dom 了 */
  return <p ref={ref}>我是函数组件</p>;
});
export default class App extends PureComponent {
  constructor(props) {
    super(props);
    this.profileRef = createRef();
  }
  render() {
    return (
      <div>
        {/* 要注意ref不是属性,和props不一样this,该属性是react内部管理 */}
        <Profile ref={this.profileRef} />
        <button onClick={(e) => this.getRef()}>获取ref</button>
      </div>
    );
  }

  getRef() {
    console.log(this.profileRef.current);
  }
}

运行结果:

image-20210928230910841

受控组件和非受控组件

在HTML中表单元素(inputtextareaselect等)通常自己维护state,并且根据用户输入进行更新。而在React中可变状态通常保存在state中来进行更新维护。我们可以把两者结合起来,使 React state 成为"唯一数据源"。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做"受控组件"。其实可以理解为vue中对于表单数据的双向绑定(v-model)

import React, { Component } from "react";

export default class Fcn extends Component {
  constructor(props) {
    super(props);
    this.state = {
      username: "zhangsan",
      fruist: "pear",
    };
  }
  render() {
    return (
      <div>
        <form onSubmit={(e) => this.handleSubmit(e)}>
          <label htmlFor="username">
            用户:
            <input
              id="username"
              type="text"
              value={this.state.username}
              onChange={this.gethandleChange("username")}
            ></input>
            {/* react中select加上value可设置默认值,原生的是在option标签上加selected */}
            <select
              name="fruits"
              onChange={this.gethandleChange("fruist")}
              value={this.state.fruist}
            >
              <option value="apple">苹果</option>
              <option value="banana">香蕉</option>
              <option value="pear">梨子</option>
            </select>
          </label>
          <input type="submit" value="提交" />
        </form>
      </div>
    );
  }
  handleSubmit(e) {
    //取消默认行为
    e.preventDefault();
    console.log(this.state.username);
    console.log(this.state.fruist);
  }
  gethandleChange(valueType) {
    return (e) => {
      this.setState({
        //计算属性名获取属性名称,将多种输入框的受控函数合并
        [valueType]: e.target.value,
      });
    };
  }
}

当然表单数据将交由 DOM 节点本身来处理就称为非受控组件。一般还是推荐使用受控组件。

Portals的使用

我们所写的DOM元素都是挂载在idroot的根元素上,但是有些DOM结构我们希望它独立的挂载到父元素或者根元素以外的地方,那么就可以使用Portals这个API。

在父组件中我们希望将Modal这个组件中的子元素渲染到指定id的元素内。

class Home extends Component {
  render() {
    return (
      <h2>
        home
        <Modal>
          {/* 需求,将所有的Modal中的子组件渲染到屏幕中间中去 */}
          <h2>modal</h2>
        </Modal>
      </h2>
    );
  }
}

我们可以在Modal组件中使用这个api

class Modal extends Component {
  render() {
    /* 将子组件挂载到#modal, */
    return ReactDOM.createPortal(
      this.props.children,
      document.getElementById("modal")
    );
  }
}

public/index.html中设置

  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <div id="modal"></div>
  </body>

渲染结果:
image-20211002212402583

fragment的使用

我们在书写jsx语法是,返回的DOM元素是必须要一个跟标签包裹,比如div,但是这个div除了满足语法外没有任何的作用,所以使用fragment就可以解决这个问题。

这个标签使用React内部自带

import React,{ Fragment } from "react";
function foo() {
    return (
      <Fragment>
        <div></div>
        <input/>
      </Fragment>
    )
}

该标签并不会被渲染成任何元素,如果不加key属性可以简写为<></>,如果加上key就需要完整的写法。

StrictMode

这个组件不会被渲任何元素,主要作用为检查子元素的潜在问题。类似JS的严格模式。

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

主要检测内部的子元素是否使用了不安全的生命周期,过时的refAPI和一些废弃的方法和API。还可以检测意外的副作用,比如组件的constructor就会执行两次来确定是否产生副作用。

Last modification:November 1, 2021
如果觉得我的文章对你有用,请随意赞赏