我学会了,react上手知识点(上篇)

简介: 本篇文章记录的是上手react并做好一个React项目的大概知识点,比如jsx本质、生命周期、组件嵌套、父子通信、组件通信、插槽机制、跨组件通信、setState、react性能优化、ref、受控和非受控组件、高阶组件、React中使用样式。

前言

本篇文章记录的是上手react并做好一个React项目的大概知识点,比如jsx本质、生命周期、组件嵌套、父子通信、组件通信、插槽机制、跨组件通信、setState、react性能优化、ref、受控和非受控组件、高阶组件、React中使用样式。

jsx 本质

jsx 本质就是React.createElement + vdom,最终返回一个初始化好的react的实例对象。就像vue中的模板语法,其实也是h函数加vdom,最终返回一个初始化好的vue的实例对象一样。

const dom = React.createElement('div',{ id: 'test-dom'}, 'AAAA')
console.log('dom', dom)

const dom2 = <div id='test-dom'>AAAA</div>
console.log('dom2', dom2)

生命周期

getDerivedStateFromProps 允许你在render之前改一下state,返回null就不更新state。
shouldComponentUpdate 是否需要渲染
render 渲染
componentDidMount 渲染之后
componentDidUpdate 更新之后
componentWillUnmount 即将卸载
getSnapshotBeforeUpdate 可以获得上一次真实渲染的快照。

组件嵌套

函数组件和类组件可以嵌套

import React, { Component } from 'react';

// Header
function Header() {
  return <h2>Header组件</h2>
}

// Main
function Banner() {
  return <h3>Banner组件</h3>
}

function ProductList() {
  return (
    <ul>
      <li>列表1</li>
      <li>列表2</li>
    </ul>
  )
}

function Main() {
  return (
    <div>
      <Banner/>
      <ProductList/>
    </div>
  )
}

// Footer
function Footer() {
  return <h2>我是Footer组件</h2>
}


export default class App extends Component {
  render() {
    return (
      <div>
        <Header/>
        <Main/>
        <Footer/>
      </div>
    )
  }
}

父传子通信

分为类组件传值,和函数组件传值,还有属性的验证。

类组件

import React, { Component } from 'react';


class ChildCpn extends Component {
  constructor() {
    super();
  }

  componentWillMount() {

  }

  componentDidMount() {
    console.log(this.props, "componentDidMount");
  }

  render() {
    // console.log(this.props, "render");
    const {name, age, height} = this.props;
    return (
      <h2>子组件展示数据: {name + " " + age + " " + height}</h2>
    )
  }
}

export default class App extends Component {
  render() {
    return (
      <div>
        <ChildCpn name="m2" age="26" height="1.88" />
        <ChildCpn name="diao" age="27" height="1.98" />
      </div>
    )
  }
}

函数组件

import React, { Component } from 'react';

function ChildCpn(props) {
  const { name, age, height } = props;

  return (
    <h2>{name + age + height}</h2>
  )
}

export default class App extends Component {
  render() {
    return (
      <div>
        <ChildCpn name="m2" age="26" height="1.88" />
        <ChildCpn name="diao" age="27" height="1.98" />
      </div>
    )
  }
}

属性验证

import React, { Component } from 'react';

import PropTypes from 'prop-types';

function ChildCpn(props) {
  const { name, age, height } = props;
  console.log(name, age, height);
  const { names } = props;

  return (
    <div>
      <h2>{name + age + height}</h2>
      <ul>
        {
          names.map((item, index) => {
            return <li>{item}</li>
          })
        }
      </ul>
    </div>
  )
}

class ChildCpn2 extends Component {
  // es6中的class fields写法
  static propTypes = {

  }

  static defaultProps = {

  }
}

ChildCpn.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number,
  height: PropTypes.number,
  names: PropTypes.array
}

// 默认值
ChildCpn.defaultProps = {
  name: "m2",
  age: 30,
  height: 1.98,
  names: ["mmm", "2222"]
}

export default class App extends Component {
  render() {
    return (
      <div>
        <ChildCpn name="m2" age={18} height={1.88} names={["m2", "mm22"]}/>
        <ChildCpn name="diao" age={40} height={1.98} names={["aiyou", "diao"]}/>
        <ChildCpn/>
      </div>
    )
  }
}

子父通信

子父通信一般是函数传递。

import React, { Component } from 'react';


class CounterButton extends Component {
  render() {
    const {onClick} = this.props; // 父组件传递过来的函数
    return <button onClick={onClick}>+1</button>
  }
}


export default class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      counter: 0
    }
  }

  render() {
    return (
      <div>
        <h2>当前计数: {this.state.counter}</h2>
        <button onClick={e => this.increment()}>+</button>
        {/* 给子组件传递一个函数 */}
        <CounterButton onClick={e => this.increment()} name="diao"/>
      </div>
    )
  }

  increment() {
    this.setState({
      counter: this.state.counter + 1
    })
  }
}

组件通信

这种通过函数传递数据的方式,在vue中也可以,都是通过自定义事件来完成,只不过vue中可以使用watch或者computed来监听,而react中是通过getDerivedStateFromProps来进行监听。

TabControl

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

export default class TabControl extends Component {
  constructor(props) {
    super(props);

    this.state = {
      currentIndex: 0
    }
  }

  render() {
    const { titles } = this.props;
    const {currentIndex} = this.state;

    return (
      <div className="tab-control">
        {
          titles.map((item, index) => {
            return (
              <div key={item} 
                   className={"tab-item " + (index === currentIndex ? "active": "")}
                   onClick={e => this.itemClick(index)}>
                <span>{item}</span>
              </div>
            )
          })
        }
      </div>
    )
  }

  itemClick(index) {
    this.setState({
      currentIndex: index
    })

    const {itemClick} = this.props;
    itemClick(index);
  }
}

TabControl.propTypes = {
  titles: PropTypes.array.isRequired
}

App

import React, { Component } from 'react';

import TabControl from './TabControl';

export default class App extends Component {
  constructor(props) {
    super(props);

    this.titles = ['新款', '精选', '流行'];

    this.state = {
      currentTitle: "新款",
      currentIndex: 0
    }
  }

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

    return (
      <div>
        <TabControl itemClick={index => this.itemClick(index)} titles={this.titles} />
        <h2>{currentTitle}</h2>
      </div>
    )
  }

  itemClick(index) {
    this.setState({
      currentTitle: this.titles[index]
    })
  }
}

插槽机制

众所周知vue中有插槽机制,但是react中也能够实现插槽,无论是普通插槽还是具名插槽都可以。

App

import React, { Component } from 'react';

import NavBar from './NavBar';
import NavBar2 from './NavBar2';

export default class App extends Component {

  render() {
    const leftJsx = <span>aaa</span>;
    return (
      <div>
       
            {/* 普通插槽 */}
            <NavBar name="" title="" className="">
            <span>aaa</span>
            <strong>bbb</strong>
            <a href="/#">ccc</a>
            </NavBar>
            
            {/* 具名插槽 */}
            <NavBar2 leftSlot={leftJsx}
                    centerSlot={<strong>bbb</strong>}
                    rightSlot={<a href="/#">ccc</a>} />
      </div>
    )
  }
}

NavBar

import React, { Component } from 'react'

export default class NavBar extends Component {
  render() {
    // this.props.children;
    return (
      <div className="nav-item nav-bar">
        <div className="nav-left">
          {/* 通过children来取出父组件传递的内容,这里是通过索引取出其中索引为1的内容,直接整个children也行 */}
          {this.props.children[0]}
        </div>
        <div className="nav-item nav-center">
          {this.props.children[1]}
        </div>
        <div className="nav-item nav-right">
          {this.props.children[2]}
        </div>
      </div>
    )
  }
}

NavBar2

import React, { Component } from 'react'

export default class NavBar2 extends Component {
  render() {
    const {leftSlot, centerSlot, rightSlot} = this.props;

    return (
      <div className="nav-item nav-bar">
        <div className="nav-left">
          {leftSlot}
        </div>
        <div className="nav-item nav-center">
          {centerSlot}
        </div>
        <div className="nav-item nav-right">
          {rightSlot}
        </div>
      </div>
    )
  }
}

跨组件通信

props 通信

import React, { Component } from 'react';

function ProfileHeader(props) {
  return (
    <div>
      <h2>昵称: {props.nickname}</h2>
      <h2>等级: {props.level}</h2>
    </div>
  )
}

function Profile(props) {
  return (
    <div>
      {/* <ProfileHeader nickname={ props.nickname } level={ props.level }/> */}
      <ProfileHeader {...props}/>
      <ul>
        <li>设置1</li>
        <li>设置2</li>
      </ul>
    </div>
  )
} 

export default class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      nickname: "kobe",
      level: 99
    }
  }

  render() {
    // const {nickname, level} = this.state;

    return (
      <div>
        {/* 通过... 可以 以平铺的方式给组件赋值 */}
        <Profile {...this.state}/>
      </div>
    )
  }
}

context 通信

创建context,设置contextType。不使用UserContext.Provider就是传默认值,使用UserContext.Provider可以传递自定义值。


import React, { Component } from 'react';

// 创建Context对象
const UserContext = React.createContext({
  nickname: "m2",
  level: -1
})

class ProfileHeader extends Component {
  render() {
    console.log(this.context);
    // jsx -> 
    return (
      <div>
        <h2>昵称: {this.context.nickname}</h2>
        <h2>等级: {this.context.level}</h2>
      </div>
    )
  }
}

ProfileHeader.contextType = UserContext;

function Profile(props) {
  return (
    <div>
      <ProfileHeader />
      <ul>
        <li>设置1</li>
        <li>设置2</li>
      </ul>
    </div>
  )
}

export default class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      nickname: "diao",
      level: 99
    }
  }

  render() {
    return (
      <div>
          {/* 给设置了contextType的注入state */}
        <UserContext.Provider value={this.state}>
            <Profile />
        </UserContext.Provider>
        <hr/>
        {/* 没使用Context。Provider 就是默认值 */}
        <Profile />
      </div>
    )
  }
}

context 函数通信

创建context,通过UserContext.Consumer中包裹函数的方式使用传递过来的值。不使用UserContext.Provider就是传默认值,使用UserContext.Provider可以传递自定义值。

import React, { Component } from 'react';

// 创建Context对象
const UserContext = React.createContext({
  nickname: "m2",
  level: -1
})

function ProfileHeader() {
  return (
    <UserContext.Consumer>
      {
        value => {
          return (
            <div>
              <h2>昵称: {value.nickname}</h2>
              <h2>等级: {value.level}</h2>
            </div>
          )
        }
      }
    </UserContext.Consumer>
  )
}

function Profile(props) {
  return (
    <div>
      <ProfileHeader />
      <ul>
        <li>设置1</li>
        <li>设置2</li>
      </ul>
    </div>
  )
}

export default class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      nickname: "diao",
      level: 99
    }
  }

  render() {
    return (
      <div>
        <UserContext.Provider value={this.state}>
          <Profile />
        </UserContext.Provider>
        <hr/>
        <Profile />
      </div>
    )
  }
}

多context 通信

和context 函数通信一样,但是可以多层嵌套Provider 和 Consumer来用。

import React, { Component } from 'react';

// 创建Context对象
const UserContext = React.createContext({
  nickname: "aaaa",
  level: -1
})

const ThemeContext = React.createContext({
  color: "black"
})

function ProfileHeader() {
  // jsx -> 嵌套的方式
  return (
    <UserContext.Consumer>
      {
        value => {
          return (
            <ThemeContext.Consumer>
              {
                theme => {
                  return (
                    <div>
                      <h2 style={{color: theme.color}}>用户昵称: {value.nickname}</h2>
                      <h2>用户等级: {value.level}</h2>
                      <h2>颜色: {theme.color}</h2>
                    </div>
                  )
                }
              }
            </ThemeContext.Consumer>
          )
        }
      }
    </UserContext.Consumer>
  )
}

function Profile(props) {
  return (
    <div>
      <ProfileHeader />
      <ul>
        <li>设置1</li>
        <li>设置2</li>
      </ul>
    </div>
  )
}

export default class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      nickname: "kobe",
      level: 99
    }
  }

  render() {
    return (
      <div>
        <UserContext.Provider value={this.state}>
          <ThemeContext.Provider value={{ color: "red" }}>
            <Profile />
          </ThemeContext.Provider>
        </UserContext.Provider>
      </div>
    )
  }
}

全局事件传递EventBus

其实就是一个自定义观察者,一端监听,一端触发这样子,就像是vue中的总线传值EventBus。

import React, { PureComponent } from 'react';

import { EventEmitter } from 'events';

// 事件总线: event bus
const eventBus = new EventEmitter();

class Home extends PureComponent {
  componentDidMount() {
    eventBus.addListener("sayHello", this.handleSayHelloListener);
  }

  componentWillUnmount() {
    eventBus.removeListener("sayHello", this.handleSayHelloListener);
  }

  handleSayHelloListener(num, message) {
    console.log(num, message);
  }

  render() {
    return (
      <div>
        Home
      </div>
    )
  }
}

class Profile extends PureComponent {
  render() {
    return (
      <div>
        Profile
        <button onClick={e => this.emmitEvent()}>点击了profile按钮</button>
      </div>
    )
  }

  emmitEvent() {
    eventBus.emit("sayHello", 123, "Hello Home");
  }
}

export default class App extends PureComponent {
  render() {
    return (
      <div>
        <Home/>
        <Profile/>
      </div>
    )
  }
}

setState

说setStae 之前,先讲讲 state 不可变,不要去直接对this.state对象做更改。更不要更改之后再去调用setState方法。

正确的应该是拷贝一份新的state数据,然后在setState把新的数据传进去这样子。

异步setState

setState在在React的合成事件以及hook中是异步的,但在合成事件和hook中也有两种方式来获取更新后的state值。
第一种是在componentDidUpdate生命周期中获取,另一种是在setState函数的第二个回调中获取更新后的state值。

import React, { Component } from 'react'

function Home(props) {
    // 我还没更新呢
    return <h1>{props.message}</h1>
  }
  
export default class App extends Component {
    constructor(props) {
      super(props);
  
      this.state = {
        message: "我还没更新呢"
      }
    }
  
    render() {
      return (
        <div>
          <h2>当前计数: {this.state.message}</h2>
          <button onClick={e => this.changeText()}>改变文本</button>
          <Home message={this.state.message}/>
        </div>
      )
    }
  
    componentDidUpdate() {
      // 方式二: 获取异步更新的state
      console.log(this.state.message);
    }
  
    changeText() {
      // 由于setState是异步更新
    //   this.setState({
    //     message: "你好啊,李银河"
    //   })
    //   console.log(this.state.message); // 同步代码先执行,所以打印 我还没更新呢
  
      // 方式一: 获取异步更新后的数据
      // setState(更新的state, 回调函数)
      this.setState({
        message: "我更新了"
      }, () => {
        console.log(this.state.message);
      })
    }
  }

同步setState

由于setState是模拟异步来维持一个任务队列,然后批量执行任务队列中的任务,但这只能够在React的合成事件及hook中来模拟,所以当你在原生的setTimout中或者原生的事件中使用setState,就会变成同步了。

import React, { Component } from 'react'

export default class App extends Component {
    constructor(props) {
        super(props);

        this.state = {
            message: "Hello World"
        }
    }

    render() {
        return (
            <div>
                <h2>{this.state.message}</h2>
                <button onClick={e => this.changeText()}>改变文本</button>
                <button id="btn">改变文本2</button>
            </div>
        )
    }

    componentDidMount() {
        // 情况二:原生的事件中使用setState
        document.getElementById("btn").addEventListener("click", (e) => {
            this.setState({
                message: "原生事件"
            })
            console.log(this.state.message);
        })

        // this.setState({
        //   message: "你好啊,李银河"
        // })
        // console.log(this.state.message);
    }

    changeText() {
        // 情况一: 将setState放入到定时器中
        setTimeout(() => {
            Promise.resolve().then(() => {console.log('我是微任务,如果有setState是异步,我会先输出Hello World,但它不是,所以输出:', this.state.message)})
            this.setState(() => ({
                message: "定时器"
            }))
            
            console.log(this.state.message);
        }, 0);
    
    }
}

setState数据合并

setState是模拟异步来维持一个任务队列,然后批量执行任务队列中的任务。

他是怎么合并多个setState的呢?
答案是:Object.assign({}, this.state, {message: "m2 aiyoudiao"})

setState自身合并

多次SetState会被进行合并,所以会导致你多次加加都只加一次,如果你想在setState合并时进行累加,可以使用setState中的参数,可以拿到上一次setState的结果。

import React, { Component } from 'react'

export default class App extends Component {
    constructor(props) {
      super(props);
  
      this.state = {
        counter: 0
      }
    }
  
    render() {
      return (
        <div>
          <h2>当前计数: {this.state.counter}</h2>
          <button onClick={e => this.increment()}>+1</button>
        </div>
      )
    }
  
    increment() {
      // 1.setState本身被合并
    //   this.setState({
    //     counter: this.state.counter + 1
    //   });
    //   this.setState({
    //     counter: this.state.counter + 1
    //   });
    //   this.setState({
    //     counter: this.state.counter + 1
    //   });
  
      // 2.setState合并时进行累加
      this.setState((prevState, props) => {
        return {
          counter: prevState.counter + 1
        }
      });
      this.setState((prevState, props) => {
        return {
          counter: prevState.counter + 1
        }
      });
      this.setState((prevState, props) => {
        return {
          counter: prevState.counter + 1
        }
      });
    }
  }

React 性能优化

key的唯一

react中遍历的时候需要给元素添加key,这是用来提高性能的,因为有了key之后,可以做到精准替换,只替换key相同但有其它变化的节点。
如果没有key,或者key是遍历时的index,那么在diff对比时,元素的顺序不对就会导致整个列表全部替换。

import React, { Component } from 'react'

export default class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      movies: ["天罗地网", "大卫天龙"]
    }
  }

  render() {
    return (
      <div>
        <h2>电影列表</h2>
        <ul>
          {
            this.state.movies.map((item, index) => {
            // 通过观察浏览器开发人员工具中的element中的元素变化。
            //   return <li key={index}>{item}</li>
              return <li key={item}>{item}</li>
            })
          }
        </ul>
        <button onClick={e => this.insertMovie()}>添加</button>
      </div>
    )
  }

  insertMovie() {
    // this.setState({
    //   movies: [...this.state.movies, "大话西游"]
    // })
    
    // 替换一下顺序
    this.setState({
      movies: ["斗罗大陆", ...this.state.movies]
    })
  }
}

组件嵌套的render调用

调用的过程是深度优先遍历的,从上到下,从外到内。
只要state一发生变化,就会从头到尾的去执行整个页面所有组件的render函数。
虽然页面几乎无感,但是去递归执行那么多组件的钩子,的确是耗性能。

shouldComponentUpdate

shouldComponentUpdate:组件是否需要更新

当这个react生命周期钩子中返回false时,则不会执行后面的render操作,这样性能就得到了提升。

它是通过你在钩子函数内部写一些判断,判断当前组件的state是否发生变化,如若没有变化,则不需要更新组件了。

import React, {Component} from 'react';

export default class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      counter: 0,
      message: "Hello World"
    }
  }

  render() {
    console.log("App render函数被调用");
    return (
      <div>
        <h2>当前计数: {this.state.counter}</h2>
        <button onClick={e => this.increment()}>+1</button>
        <button onClick={e => this.changeText()}>改变文本</button>
      </div>
    )
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (this.state.counter !== nextState.counter) {
      return true;
    }
    
    return false;
  }

  increment() {
    this.setState({
      counter: this.state.counter + 1
    })
  }

  changeText() {
    this.setState({
      message: "m2"
    })
  }
}

PureComponent

它是基于shouldComponentUpdate来做的性能优化,也就是当前组件的state未发生变化时,就不会执行后面的render操作。他也是通过shouldComponentUpdate来实现的。
基本原理:把新旧的props和新旧的state进行一个浅对比(源码中只比较了第一层,没做递归对比),如果没有发生变化,就不执行render函数。

import React, { PureComponent } from 'react';

// Header
function Header() {
  console.log("Header被调用");
  return <h2>我是Header组件</h2>
}

// Main
class Banner extends PureComponent {
  render() {
    console.log("Banner render函数被调用");
    return <h3>我是Banner组件</h3>
  }
}

function ProductList() {
  console.log("ProductList被调用");
  return (
    <ul>
      <li>商品列表1</li>
    </ul>
  )
}

class Main extends PureComponent {
  render() {
    console.log("Main render函数被调用");
    return (
      <div>
        <Banner/>
        <ProductList/>
      </div>
    )
  }
}

// Footer
function Footer() {
  console.log("Footer被调用");
  return <h2>我是Footer组件</h2>
}


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

    this.state = {
      counter: 0
    }
  }

  render() {
    console.log("App render函数被调用");
    return (
      <div>
        <h2>当前计数: {this.state.counter}</h2>
        <button onClick={e => this.increment()}>+1</button>
        <Header/>
        <Main/>
        <Footer/>
      </div>
    )
  }

  increment() {
    this.setState({
      counter: this.state.counter + 1
    })
  }
}

memo

memo的作用和PureComponent类似,只是memo作用于函数式组件,而PureComponent作用于class组件。

memo 可以用于包裹一个函数式组件,然后生成一个新的函数,这个函数会在执行render之前去浅对比一下新旧props,没有变化就不会执行render操作了。

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

// Header
const MemoHeader = memo(function Header() {
  console.log("Header被调用");
  return <h2>我是Header组件</h2>
})


// Main
class Banner extends PureComponent {
  render() {
    console.log("Banner render函数被调用");
    return <h3>我是Banner组件</h3>
  }
}

const MemoProductList = memo(function ProductList() {
  console.log("ProductList被调用");
  return (
    <ul>
      <li>商品列表1</li>
    </ul>
  )
})

class Main extends PureComponent {
  render() {
    console.log("Main render函数被调用");
    return (
      <div>
        <Banner/>
        <MemoProductList/>
      </div>
    )
  }
}

// Footer
const MemoFooter = memo(function Footer() {
  console.log("Footer被调用");
  return <h2>我是Footer组件</h2>
})


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

    this.state = {
      counter: 0
    }
  }

  render() {
    console.log("App render函数被调用");
    return (
      <div>
        <h2>当前计数: {this.state.counter}</h2>
        <button onClick={e => this.increment()}>+1</button>
        <MemoHeader/>
        <Main/>
        <MemoFooter/>
      </div>
    )
  }

  increment() {
    this.setState({
      counter: this.state.counter + 1
    })
  }
}

ref拿到原生DOM和组件实例

通过createRef创建一个ref对象,然后在标签中设置ref属性为这个ref对象,之后就可以通过这个ref对象来拿到原生dom了。也可以拿到react组件实例对象。

另外也可以通过ref属性绑定回调的方式来拿到原生的dom对象。


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

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

    this.state = {
      counter: 0
    }
  }

  render() {
    return (
      <div>
        <h2>当前计数: {this.state.counter}</h2>
        <button onClick={e => this.increment()}>+1</button>
      </div>
    )
  }

  increment() {
    this.setState({
      counter: this.state.counter + 1
    })
  }
}

export default class App extends PureComponent {

  constructor(props) {
    super(props);

    this.titleRef = createRef();
    this.counterRef = createRef();
    this.titleEl = null;
  }

  render() {
    return (
      <div>
        {/* <h2 ref=字符串/对象/函数>Hello React</h2> */}
        <h2 ref="titleRef">Hello React</h2>
        {/* 目前React推荐的方式 */}
        <h2 ref={this.titleRef}>Hello React</h2>
        <h2 ref={arg => this.titleEl = arg}>Hello React</h2>
        <button onClick={e => this.changeText()}>改变文本</button>
        <hr/>
        <Counter ref={this.counterRef}/>
        <button onClick={e => this.appBtnClick()}>App按钮</button>
      </div>
    )
  }

  changeText() {
    // 1.使用方式一: 字符串(不推荐, 后续的更新会删除)
    this.refs.titleRef.innerHTML = "Hello Coderm2";
    // 2.使用方式二: 对象方式
    this.titleRef.current.innerHTML = "Hello JavaScript";
    // 3.使用方式三: 回调函数方式
    this.titleEl.innerHTML = "Hello TypeScript";
  }

  appBtnClick() {
    this.counterRef.current.increment();
  }
}

受控于非受控组件

受控组件就是你可以通过给表单元素绑定state上的值,然后再给他绑定React的合并事件。从而实现vue那样的响应式效果。

非受控组件就是你不给他绑定state上的值,也不给他绑定React的合并事件,从而这个表单元素就不受控了,如此一来,你只能通过ref来获取原生dom的数据了。

受控

import React, { PureComponent } from 'react'

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

    this.state = {
      username: "",
      password: "",
      fruits: "apple"
    }
  }

  render() {
    return (
      <div>
        <form onSubmit={e => this.handleSubmit(e)}>
          <label htmlFor="username">
            用户: 
            <input type="text" 
                   id="username"
                   name="username" 
                   onChange={e => this.handleChange(e)}
                   value={this.state.username}/>
          </label>
          <br/>
          <label htmlFor="password">
            密码: 
            <input type="text" 
                   id="password" 
                   name="password" 
                   onChange={e => this.handleChange(e)}
                   value={this.state.password}/>
          </label>
          <br/>
          <label htmlFor="fruits">
            选择: 
            <select name="fruits" 
                    id="fruits"
                    onChange={e => this.handleChange(e)}
                    value={this.state.fruits}>
                <option value="apple">苹果</option>
                <option value="banana">香蕉</option>
                <option value="orange">橘子</option>
            </select>
          </label>

          <br/>
          <input type="submit" value="提交"/>
        </form>
      </div>
    )
  }

  handleSubmit(event) {
    event.preventDefault();
    const {username, password, fruits} = this.state;
    console.log(username, password, fruits);
  }

  handleChange(event) {
    this.setState({
      // 计算属性名
      [event.target.name]: event.target.value
    })
  }

  // handleUsernameChange(event) {
  //   this.setState({
  //     username: event.target.value
  //   })
  // }

  // handlePasswordChange(event) {
  //   this.setState({
  //     password: event.target.value
  //   })
  // }

  // handleFruitsChange(event) {
  //   this.setState({
  //     fruits: event.target.value
  //   })
  // }

}

非受控

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

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

    this.usernameRef = createRef();
  }

  render() {
    return (
      <div>
        <form onSubmit={e => this.handleSubmit(e)}>
          <label htmlFor="username">
            用户: 
            <input type="text" id="username" ref={this.usernameRef}/>
          </label>
          <input type="submit" value="提交"/>
        </form>
      </div>
    )
  }

  handleSubmit(event) {
    event.preventDefault();
    console.log(this.usernameRef.current.value);
  }
}

高阶组件

高阶组件本质就是面向对象中装饰器的功能,给某一个类追加新功能,同时也会返回一个新的拓展过新功能的类。

基础版

import React, { PureComponent } from 'react'

class App extends PureComponent {
  render() {
    return (
      <div>
        App: {this.props.name}
      </div>
    )
  }
}

// class 方式
function enhanceComponent(WrappedComponent) {
  class NewComponent extends PureComponent {
    render() {
      return <WrappedComponent {...this.props}/>
    }
  }

  NewComponent.displayName = "m2";
  return NewComponent;
}

// function 方式
function enhanceComponent2(WrappedComponent) {
  function NewComponent(props) {
    return <WrappedComponent {...props}/>
  }

  NewComponent.displayName = "m2";
  return NewComponent;
}

const EnhanceComponent = enhanceComponent2(App);

export default EnhanceComponent;

增强props

import React, { PureComponent } from 'react';

// 定义一个高阶组件
function enhanceRegionProps(WrappedComponent) {
  return props => {
    return <WrappedComponent {...props} region="中国"/>
  }
}

class Home extends PureComponent {
  render() {
    return <h2>Home: {`昵称: ${this.props.nickname} 等级: ${this.props.level} 区域: ${this.props.region}`}</h2>
  }
}


class About extends PureComponent {
  render() {
    return <h2>About: {`昵称: ${this.props.nickname} 等级: ${this.props.level} 区域: ${this.props.region}`}</h2>
  }
}


const EnhanceHome = enhanceRegionProps(Home);
const EnhanceAbout = enhanceRegionProps(About);

class App extends PureComponent {
  render() {
    return (
      <div>
        App
        <EnhanceHome nickname="coderm2" level={90}/>
        <EnhanceAbout nickname="kobe" level={99}/>
      </div>
    )
  }
}

export default App;

使用高阶组件使用Context来增强

使用前

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

// 创建Context
const UserContext = createContext({
  nickname: "默认",
  level: -1,
  区域: "中国"
});
class Home extends PureComponent {
  render() {
    return (
      <UserContext.Consumer>
        {
          user => {
            return <h2>Home: {`昵称: ${user.nickname} 等级: ${user.level} 区域: ${user.region}`}</h2>
          } 
        }
      </UserContext.Consumer>
    )
  }
}

class About extends PureComponent {
  render() {
    return (
      <UserContext.Consumer>
        {
          user => {
            return <h2>About: {`昵称: ${user.nickname} 等级: ${user.level} 区域: ${user.region}`}</h2>
          } 
        }
      </UserContext.Consumer>
    )
  }
}

class App extends PureComponent {
  render() {
    return (
      <div>
        App
        <UserContext.Provider value={{nickname: "m2", level: 90, region: "中国"}}>
          <Home/>
          <About/>
        </UserContext.Provider>
      </div>
    )
  }
}

export default App;

使用后

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

// 定义一个高阶组件
function withUser(WrappedComponent) {
  return props => {
    return (
      <UserContext.Consumer>
        {
          user => {
            return <WrappedComponent {...props} {...user}/>
          } 
        }
      </UserContext.Consumer>
    )
  }
}

// 创建Context
const UserContext = createContext({
  nickname: "默认",
  level: -1,
  区域: "中国"
});

class Home extends PureComponent {
  render() {
    return <h2>Home: {`昵称: ${this.props.nickname} 等级: ${this.props.level} 区域: ${this.props.region}`}</h2>
  }
}


class About extends PureComponent {
  render() {
    return <h2>About: {`昵称: ${this.props.nickname} 等级: ${this.props.level} 区域: ${this.props.region}`}</h2>
  }
}

class Detail extends PureComponent {
  render() {
    return (
      <ul>
        <li>{this.props.nickname}</li>
        <li>{this.props.level}</li>
        <li>{this.props.region}</li>
      </ul>
    )
  }
}


const UserHome = withUser(Home);
const UserAbout = withUser(About);
const UserDetail = withUser(Detail);

class App extends PureComponent {
  render() {
    return (
      <div>
        App
        <UserContext.Provider value={{nickname: "m2", level: 90, region: "中国"}}>
          <UserHome/>
          <UserAbout/>
          <UserDetail/>
        </UserContext.Provider>
      </div>
    )
  }
}

export default App;

高阶组件实现登录鉴权


import React, { PureComponent } from 'react';

class LoginPage extends PureComponent {
  render() {
    return <h2>LoginPage</h2>
  }
}

function withAuth(WrappedComponent) {
  const NewCpn = props => {
    const {isLogin} = props;
    if (isLogin) {
      return <WrappedComponent {...props}/>
    } else {
      return <LoginPage/>
    }
  }

  NewCpn.displayName = "AuthCpn"

  return NewCpn;
}

// 购物车组件
class CartPage extends PureComponent {
  render() {
    return <h2>CartPage</h2>
  }
}

const AuthCartPage = withAuth(CartPage);

export default class App extends PureComponent {
  render() {
    return (
      <div>
        <AuthCartPage isLogin={true}/>
      </div>
    )
  }
}

通过高阶组件来劫持生命周期

使用前

import React, { PureComponent } from 'react';

class Home extends PureComponent {

  // 即将渲染获取一个时间 beginTime
  UNSAFE_componentWillMount() {
    this.beginTime = Date.now();
  }

  // 渲染完成再获取一个时间 endTime
  componentDidMount() {
    this.endTime = Date.now();
    const interval = this.endTime - this.beginTime;
    console.log(`Home渲染时间: ${interval}`)
  }

  render() {
    return <h2>Home</h2>
  }
}


class About extends PureComponent {
  // 即将渲染获取一个时间 beginTime
  UNSAFE_componentWillMount() {
    this.beginTime = Date.now();
  }

  // 渲染完成再获取一个时间 endTime
  componentDidMount() {
    this.endTime = Date.now();
    const interval = this.endTime - this.beginTime;
    console.log(`About渲染时间: ${interval}`)
  }

  render() {
    return <h2>About</h2>
  }
}

export default class App extends PureComponent {
  render() {
    return (
      <div>
        <Home />
        <About />
      </div>
    )
  }
}

使用后

import React, { PureComponent } from 'react';

function withRenderTime(WrappedComponent) {
  return class extends PureComponent {
    // 即将渲染获取一个时间 beginTime
    UNSAFE_componentWillMount() {
      this.beginTime = Date.now();
    }

    // 渲染完成再获取一个时间 endTime
    componentDidMount() {
      this.endTime = Date.now();
      const interval = this.endTime - this.beginTime;
      console.log(`${WrappedComponent.name}渲染时间: ${interval}`)
    }

    render() {
      return <WrappedComponent {...this.props}/>
    }
  }
}

class Home extends PureComponent {
  render() {
    return <h2>Home</h2>
  }
}


class About extends PureComponent {
  render() {
    return <h2>About</h2>
  }
}

const TimeHome = withRenderTime(Home);
const TimeAbout = withRenderTime(About);

export default class App extends PureComponent {
  render() {
    return (
      <div>
        <TimeHome />
        <TimeAbout />
      </div>
    )
  }
}

class Person {

}

console.log(Person.name);

高阶组件forwardRef

这个组件支持代理转发ref

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

class Home extends PureComponent {
  render() {
    return <h2>Home</h2>
  }
}

// 高阶组件forwardRef
const Profile = forwardRef(function(props, ref) {
  return <p ref={ref}>Profile</p>
})

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

    this.titleRef = createRef();
    this.homeRef = createRef();
    this.profileRef = createRef();
  }

  render() {
    return (
      <div>
        <h2 ref={this.titleRef}>Hello World</h2>
        <Home ref={this.homeRef}/>

        <Profile ref={this.profileRef} name={"why"}/>

        <button onClick={e => this.printRef()}>打印ref</button>
      </div>
    )
  }

  printRef() {
    console.log(this.titleRef.current);
    console.log(this.homeRef.current);
    console.log(this.profileRef.current);
  }
}

portals 门户组件

这个组件可以将当前节点挂载到父组件之外,类似于vue3中的Teleport组件。createPortal是ReactDOM的一个API。

import React, { PureComponent } from 'react';
import ReactDOM from 'react-dom';

class Modal extends PureComponent {
  render() {
    return ReactDOM.createPortal(
      this.props.children,
      document.getElementById("modal")
    )
  }
}

class Home extends PureComponent {
  render() {
    return (
      <div>
        <h2>Home</h2>
        <Modal>
          <h2>Title</h2>
        </Modal>
      </div>
    )
  }
}

export default class App extends PureComponent {
  render() {
    return (
      <div>
        <Home/>
      </div>
    )
  }
}

Fragment

这个组件是一个空组件,类似于空标签的语法<></>,不会占用任何标签位置。如果要用到key,那就只能写Fragment,不能写空标签的语法<></>

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

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

    this.state = {
      counter: 0,
      friends: [
        {name: "why", age: 18},
        {name: "lilei", age: 20},
        {name: "kobe", age: 25},
      ]
    }
  }

  render() {
    return (
      <>
        <h2>当前计数: {this.state.counter}</h2>
        <button onClick={e => this.increment()}>+1</button>
        <div>
          {
            this.state.friends.map((item, index) => {
              return (
                <Fragment key={item.name}>
                  <div>{item.name}</div>
                  <p>{item.age}</p>
                  <hr/>
                </Fragment>
              )
            })
          }
        </div>
      </>
    )
  }

  increment() {
    this.setState({
      counter: this.state.counter + 1
    })
  }
}

StrictMode

给包裹层开启React的严格模式,这样一来你就不能用一些react中的不安全的生命周期,比如UNSAFE_componentWillMount,你就得严格按照规范写。
它可以用来突出显示应用程序中潜在问题,StrictMode不会在界面中显示,它可以在开发的时候触发额外的检查和警告,但并不会影响生产构建。

主要作用:
识别不安全的生命周期
识别过时的ref API
识别废弃的findDOMNode方法
检查意外的副作用,比如把你组件constructor调用2次,看看会不会产生一些副作用,生产环境不会这样。
检查过时的context API,比如早起的Context是通过static属性声明的Context对象属性,然后通过getChildContext返回Context对象等方式来使用Context的。这种方式不好,已过时。

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

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

    console.log("home constrcutor");
  }

  // UNSAFE_componentWillMount() {
  //   console.log("home componentWillMount");
  // }

  render() {
    return (
      <div ref="title">
        Home
      </div>
    )
  }
}

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

    console.log("profile constructor");
  }

  // UNSAFE_componentWillMount() {
  //   console.log("profile componentWillMount");
  // }

  render() {
    return (
      <div ref="title">
        Profile
      </div>
    )
  }
}

export default class App extends PureComponent {
  render() {
    return (
      <div>
       {/* 只检查Home组件及其后代组件 */}
        <StrictMode>
          <Home/>
        </StrictMode>
        {/* StrictMode 包裹层之外的不检查 */}
        <Profile/>
      </div>
    )
  }
}

React 中的样式

内联样式

import React, { PureComponent } from 'react'

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

    this.state = {
      color: "red"
    }
  }

  render() {
    const pStyle = {
      color: this.state.color,
      textDecoration: "underline"
    }

    return (
      <div>
        <h2 style={{fontSize: "50px", color: "red"}}>h2 title</h2>
        <p style={pStyle}>p 是 一段描述</p>
      </div>
    )
  }
}

普通样式css引入

import React, { PureComponent } from 'react';

import './style.css';

export default class Home extends PureComponent {
  render() {
    return (
      <div className="home">
        <h2 className="title">h2 标题</h2>
        <div className="banner">
          <span>广告位</span>
        </div>
      </div>
    )
  }
}
.home .title {
  font-size: 25px;
  color: blue;
}

.home .banner {
  color: green;
}

CSS Module

转成js对象,直接.的方式用里面的class样式

import React, { PureComponent } from 'react';

import homeStyle from './style.module.css';

export default class Home extends PureComponent {
  render() {
    return (
      <div className="home">
        <h2 className={homeStyle.title}>我是home的标题</h2>
        <div className={homeStyle.banner}>
          <span>轮播图</span>
        </div>
      </div>
    )
  }
}
.home .title {
  font-size: 25px;
  color: blue;
}

.home .banner {
  color: green;
}

styled-components

先安装这个依赖 styled-components,然后用es6中的模板语法就能定义样式组件了。

import React, { PureComponent } from 'react';

import styled, { ThemeProvider } from 'styled-components';

const MyButton = styled.button`
  padding: 10px 20px;
  border-color: red;
  color: red;
  font-size: ${props => props.theme.fontSize};
`

const MyPrimaryButton = styled(MyButton)`
  color: #fff;
  background-color: ${props => props.theme.themeColor};
`

/**
 * 特点:
 *  1.props穿透
 *  2.attrs的使用
 *  3.传入state作为props属性
 */

const MyInput = styled.input.attrs({
  placeholder: "m2",
  bColor: "red"
})`
  background-color: ${props => props.theme.themeColor};
  color: ${props => props.color};
`

export default class App extends PureComponent {
  render() {
    return (
      <ThemeProvider theme={{themeColor: "blue", fontSize: "30px"}}>
       <MyInput type="password" color="red" />
        <MyButton>普通的按钮</MyButton>
        <MyPrimaryButton>主色的按钮</MyPrimaryButton>
      </ThemeProvider>
    )
  }
}

总结

内容偏长,但是比较常见,也算是React实践必备吧。jsx本质、生命周期、组件嵌套、父子通信、组件通信、插槽机制、跨组件通信、setState、react性能优化、ref、受控和非受控组件、高阶组件、React中使用样式。后面还会有 antd、transition、redux、react-redux、middleware、reducer、router、hooks、next等等。

目录
相关文章
|
3月前
|
Web App开发 存储 前端开发
React 之 Scheduler 源码中的三个小知识点,看看你知不知道?
本篇补充讲解 Scheduler 源码中的三个小知识点。
90 0
|
10月前
|
前端开发 JavaScript 测试技术
React知识点系列(5)-每天10个小知识
React知识点系列(5)-每天10个小知识
39 0
|
10月前
|
缓存 前端开发 JavaScript
React知识点系列(4)-每天10个小知识
React知识点系列(4)-每天10个小知识
20 0
|
10月前
|
前端开发 JavaScript 中间件
React知识点系列(3)-每天10个小知识
React知识点系列(3)-每天10个小知识
28 0
|
10月前
|
存储 缓存 前端开发
React知识点系列(6)-每天10个小知识
React知识点系列(6)-每天10个小知识
38 0
|
3月前
|
XML 资源调度 前端开发
React基础知识点
React基础知识点
63 0
|
3月前
|
缓存 监控 前端开发
这个知识点,是React的命脉
这个知识点,是React的命脉
|
3月前
|
XML 存储 前端开发
react部分知识点总结
react部分知识点总结
47 0
|
10月前
|
缓存 前端开发 JavaScript
React知识点系列(8)-每天10个小知识
React知识点系列(8)-每天10个小知识
34 0
|
10月前
|
缓存 前端开发 JavaScript
React知识点系列(7)-每天10个小知识
React知识点系列(7)-每天10个小知识
42 0