react摘要

基礎(chǔ)

  • jsx(原理?)
  • 實現(xiàn)一個簡單的createElement及render方法
  • 組件聲明方式
  • props和state的區(qū)別
  • 綁定事件(一些特殊的事件也可以往上冒泡,如onBlur)
  • 屬性校驗
  • setState的使用(有兩種寫法)
  • 復(fù)合組件(多個組件進行組合,父子通信、子父通信的實現(xiàn))
  • 受控與非受控組件

jsx表達式的用法

  1. 可以放JS的執(zhí)行結(jié)果
  2. 如果換行需要用()包裹jsx代碼
  3. 可以把JSX元素當(dāng)作函數(shù)的返回值
  4. <{來判斷是表達式還是js

jsx屬性

在JSX中分為普通屬性和特殊屬性,像class要寫成className,for要寫成htmlFor style要采用對象的方式, dangerouslyInnerHTML插入html

組件聲明方式

兩種,普通函數(shù)及class

屬性校驗

通常這個是會寫在一個component組件中,提供給別人使用。

prop-types

在react中props是組件對外暴露的接口,但通常組件并不會明顯的申明他會暴露那些接口及類型,這不太利于組件的復(fù)用,但比較好的是React提供了PropTypes這個對象用于校驗屬性的類型,PropTypes包含組件屬性的所有可能類型,以下我們通過一個示列來說明(對象的key是組件的屬性名,value是對應(yīng)屬性類型)組件屬性的校驗

class Person extends Component {
    // 傳的props格式不對,不會中斷頁面渲染
    static propTypes = {
        name: PropTypes.string.isRequired,
        age: PropTypes.number,
        gender: PropTypes.oneOf(['男', '女']),
        hobby: PropTypes.array,
       // 自定義類型
        salary: function (props, key, com) {
            if (props[key] < 1000) {
                throw new Error(`${com} error ${props[key]} is too low`)
            }
        },
        position: PropTypes.shape({
            x: PropTypes.number,
            y: PropTypes.number
        })
    }
    constructor(props) {
        super();
    }
    render() {
        let { name, age, gender, hobby, salary, position } = this.props;
        return (<div>
            {name}{age}
            {gender}{hobby}
            {salary} {JSON.stringify(position)}
        </div>)
    }
}

受控與非受控組件

一般受控或非受控組件,指的是表單元素,如input、select、checkbox等。

受控是指表單的值,必須通過事件+狀態(tài)來改變,比如說:

<input value="123" />

在不加onChange事件的前提下,我們是沒法將123改為其他值的。

當(dāng)有多個表單元素時,不同的元素onChange事件可以通過event.target.name來做區(qū)分(前提是每個元素需要加上name的屬性)

非受控,是指不需要通過狀態(tài)來改變,上面的代碼可以改為:

<input defaultValue="123" />

那么它在表單提交時,怎么來獲取元素值呢?答案是通過ref

ref的寫法

字符串

// 在某個方法中 this.refs.username // jsx<input ref="username"  />stylus

函數(shù)

// 在某個方法中 this.username // jsx<input ref={ref => this.username=ref}  />verilog

對象

// 在constructor里面this.username = React.createRef();// 在某個方法中this.username.current  // 這個就是dom元素// jsx<input ref={this.username} />kotlin

第一種寫法現(xiàn)在不是太推薦了,一般使用第二種或者第三種,第三種需要v16.3以上的版本。

生命周期

在網(wǎng)上找到一張圖,還是挺直觀的:


import React, { Component } from 'react';
class Counter extends React.Component {
    static defaultProps = {
        name: 'zpu'
    };
    constructor(props) {
        super();
        this.state = { number: 0 }
        console.log('1.constructor構(gòu)造函數(shù)')
    }
    componentWillMount() {
        console.log('2.組件將要加載 componentWillMount');
    }
    componentDidMount() {
        console.log('4.組件掛載完成 componentDidMount');
    }
    handleClick = () => {
        this.setState({ number: this.state.number + 1 });
    };
    shouldComponentUpdate(nextProps, nextState) { // 代表的是下一次的屬性 和 下一次的狀態(tài)
        console.log('5.組件是否更新 shouldComponentUpdate');
        return nextState.number % 2;
    }
    componentWillUpdate() {
        console.log('6.組件將要更新 componentWillUpdate');
    }
    componentDidUpdate() {
        console.log('7.組件完成更新 componentDidUpdate');
    }
    render() {
        console.log('3.render');
        return (
            <div>
                <p>{this.state.number}</p>
                {this.state.number > 3 ? null : <ChildCounter n={this.state.number} />}
                <button onClick={this.handleClick}>+</button>
            </div>
        )
    }
}
class ChildCounter extends Component {
    componentWillUnmount() {
        console.log('組件將要卸載componentWillUnmount')
    }
    componentWillMount() {
        console.log('child componentWillMount')
    }
    render() {
        console.log('child-render')
        return (<div>
            {this.props.n}
        </div>)
    }
    componentDidMount() {
        console.log('child componentDidMount')
    }
    componentWillReceiveProps(newProps) { // 第一次不會執(zhí)行,之后屬性更新時才會執(zhí)行
        console.log('child componentWillReceiveProps')
    }
    shouldComponentUpdate(nextProps, nextState) {
        console.log('child shouldComponentUpdate');
        return nextProps.n % 3; // 子組件判斷接收的屬性 是否滿足更新條件 為true則更新
    }
}
export default Counter;
// defaultProps
// constructor
// componentWillMount
// render
// componentDidMount
// 狀態(tài)更新會觸發(fā)的
// shouldComponentUpdate nextProps,nextState=>boolean
// componentWillUpdate
// componentDidUpdate
// 屬性更新
// componentWillReceiveProps newProps
// 卸載
// componentWillUnmount

關(guān)于React v16.3 新生命周期

到了react16.3,生命周期去掉了以下三個:

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

This lifecycle was previously named componentWillMount. That name will continue to work until version 17. Use the rename-unsafe-lifecycles codemod to automatically update your components.

上面像componentWillMountcomponentWillUpdate去掉一般影響不會太大,但是像componentWillReceiveProps這個就有關(guān)系了,所以react又新增了兩個生命周期:

  • static getDerivedStateFromProps
  • getSnapshotBeforeUpdate
    getDerivedStateFromProps
    getDerivedStateFromProps就是用來替代componentWillReceiveProps方法的,但是需要注意是它是靜態(tài)方法,在里面無法使用this,它會返回一個對象作為新的state,返回null則說明不需要更新state。
class Example extends React.Component {
  static getDerivedStateFromProps(nextProps, prevState) {
    // 沒錯,這是一個static
  }
}

另外它的觸發(fā)時機是:在組件構(gòu)建之后(虛擬dom之后,實際dom掛載之前) ,以及每次獲取新的props之后。和之前componentWillReceiveProps有一個區(qū)別是,后者只有獲取新的props之后,才會觸發(fā),第一次是不觸發(fā)的。

簡單例子如下:

if (nextProps.currentRow !== prevState.lastRow) {
  return {
    ...
    lastRow: nextProps.currentRow,
  };
  // 不更新state
  return null
}
getSnapshotBeforeUpdate

文檔的意思大概是使組件能夠在可能更改之前從DOM中捕獲一些信息(例如滾動位置)。簡單地說就是在更新前記錄原來的dom節(jié)點屬性,然后傳給componentDidUpdate。

componentDidCatch

我們都知道如果組件中有錯誤,那整個頁面可能就會變成空白,然后控制臺一堆紅色報錯。

在 React 16.x 版本中,引入了所謂 Error Boundary 的概念,從而保證了發(fā)生在 UI 層的錯誤不會連鎖導(dǎo)致整個應(yīng)用程序崩潰;未被任何異常邊界捕獲的異??赡軙?dǎo)致整個 React 組件樹被卸載。

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class ErrorBoundary extends Component {
    constructor(props) {
        super(props);
        this.state={hasError:false};
    }
    componentDidCatch(err,info) {
        this.setState({hasError: true});
    }
    render() {
        if (this.state.hasError) {
            return <h1>Something Went Wrong</h1>
        }
        return this.props.children;
    }
}
class Clock extends Component {
    render() {
        return (
            <div>hello{null.toString()}</div>
        )
    }
}
class Page extends Component {
    render() {
        return (
            <ErrorBoundary>
                <Clock/>
            </ErrorBoundary>
        )
    }
}
ReactDOM.render(<Page/>,document.querySelector('#root'));

上下文(context api)

傳統(tǒng)寫法

父組件:

static childContextTypes={
    color: PropTypes.string,
    changeColor:PropTypes.func
}
getChildContext() {
    return {
        color: this.state.color,
        changeColor:(color)=>{
            this.setState({color})
        }
    }
}

簡單地說,就是寫兩個東西:

聲明context類型
聲明context對象,即子組件需要的context
子組件:

static contextTypes = {
    color: PropTypes.string,
    changeColor: PropTypes.func
}  
// 使用
this.context.color;

也是先要聲明類型(這里特指需要用到的context類型,如果不需要用的話,就不需要聲明),然后使用this.context來取,就OK了。。

新式寫法

上面的傳統(tǒng)寫法,其實是有點問題的:如果某個組件shouldComponentUpdate返回了false后面的組件就不會更新了。

當(dāng)然這個我在團隊中提及,他們有些人覺得scu返回了false,應(yīng)該是不讓它去更新了,但我覺得是需要更新的。

來看看寫法吧。

// 創(chuàng)建一個消費者和提供者
let { Consumer,Provider} = React.createContext();
class Parent extends Component {
    render() {
        // Provider通過value來傳遞數(shù)據(jù)
        return (
            <Provider value={{ a: 1, b: 2 }}>
                <Son></Son>
            </Provider>
        );
    }
}
class Son extends Component {
    render() {
        // Consumer的children是一個函數(shù),函數(shù)的參數(shù)為Provider的value對象
        return (
            <Consumer>
                {
                    ({a, b}) => {
                        return (
                            <div>{a}, </div>
                        )
                    }
                }
            </Consumer>
        )
    }
}

寫法上,比傳統(tǒng)的寫法更加舒服一些。當(dāng)然實際應(yīng)用中,可能會有多個Provider、多個Consumer,然后嵌套,不過這樣層級寫多了,比較惡心

插槽(Portals)

在react16中,提供了一個方法:
ReactDOM.createPortal(child, container)
React16.0中發(fā)布了很多新特性,我們來看portal,React提供了一個頂級API—portal,用來將子節(jié)點渲染到父節(jié)點之外的dom節(jié)點

Portals 提供了一種很好的將子節(jié)點渲染到父組件以外的 DOM 節(jié)點的方式。
  
  ReactDOM.createPortal(child, container)
  
  第一個參數(shù)(child)是任何可渲染的 React 子元素,例如一個元素,字符串或碎片。第二個參數(shù)(container)則是一個 DOM 元素。
  
  From React文檔
import React from "react";
import { createPortal } from "react-dom";
import classnames from "classnames";
const rootEle = document.body;

/**
 * show: 這個屬性通過切換類名改變樣式控制組件控制彈層的出現(xiàn)/隱藏
 * onSwitch: 通過傳遞函數(shù),給予彈出層自我切換的方法
 * children: react組件自帶屬性,獲取組件的開始和結(jié)束標(biāo)記之間的內(nèi)容
 */

export default ({ show, onSwitch, children }) =>
  createPortal(
    <div
      className={classnames("modal", { "modal-show": show })}
      onClick={onSwitch}
    >
      {children}
    </div>,
    rootEle
  );

調(diào)用,在應(yīng)用中創(chuàng)建一個show狀態(tài)來管理彈出層的切換,以及switchModal方法用來對該狀態(tài)進行切換,并將這兩個屬性傳遞給彈出層組件

import React from "react";
import ReactDOM from "react-dom";
import Modal from "./Modal";
import "./styles.css";

class App extends React.PureComponent {
  state = {
    show: false
  };
  switchModal = () => this.setState({ show: !this.state.show });
  render() {
    const { show } = this.state;
    return (
      <div id="App">
        <h1 onClick={this.switchModal}>點我彈出</h1>
        <Modal show={show} onSwitch={this.switchModal}>
          點擊關(guān)閉
        </Modal>
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

片段(fragments)

React 中一個常見模式是為一個組件返回多個元素。 片段(fragments) 可以讓你將子元素列表添加到一個分組中,并且不會在DOM中增加額外節(jié)點。

舉個例子,比如說我們將一個h2元素和h3元素放到root節(jié)點去,很早之前的做法是必須要在h2和h3元素外面套一層div。但現(xiàn)在不一定非要這么做:

<React.Fragment>
       <h2></h2>
       <h3></h3>
</<React.Fragment>

這個也可以用在多個li返回上面,當(dāng)然多個li的時候,也可以返回一個數(shù)組,加上不同的key即可,如:

const items = [
    <li key="1">1</li>,
    <li key="2">2</li>,
    <li key="3">3</li>
]
ReactDOM.render((
    <React.Fragment>
        <div>aaa</div>
        <h3>bb</h3>
        {items}
    </React.Fragment>
), document.getElementById('root'));

高階組件(HOC)

HOC,全稱: Higher-Order Components。簡單地說,就是對原有的component再包裝一層,有點類似extend。

HOC(High Order Component) 是 react 中對組件邏輯復(fù)用部分進行抽離的高級技術(shù),但HOC并不是一個 React API 。 它只是一種設(shè)計模式,類似于裝飾器模式。
具體而言,HOC就是一個函數(shù),且該函數(shù)接受一個組件作為參數(shù),并返回一個新組件。
從結(jié)果論來說,HOC相當(dāng)于 Vue 中的 mixins(混合) 。其實 React 之前的策略也是采用 mixins ,但是后來 facebook 意識到 mixins 產(chǎn)生的問題要比帶來的價值大,所以移除了 mixins

Why ? 為什么使用HOC

import React, { Component } from 'react'

class Page1 extends Component{
  componentWillMount(){
    let data = localStorage.getItem('data')
    this.setState({ data })
  }

  render() {
    return (
      <h2>{this.state.data}</h2>
    )
  }
} 

export default Page1

這個例子中在組件掛載前需要在 localStorage 中取出 data 的值,但當(dāng)其他組件也需要從 localStorage 中取出同樣的數(shù)據(jù)進行展示的話,每個組件都需要重新寫一遍 componentWillMount 的代碼,那就會顯得非常冗余。那么在 Vue 中通常我們采用:
mixins: []
但是在 React 中我們需要采用HOC模式咯

import React, { Component } from 'react'

const withStorage = WrappedComponent => {
  return class extends Component{
    componentWillMount() {
      let data = localStorage.getItem('data')
      this.setState({ data })
    }

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

export default withStorage

當(dāng)我們構(gòu)建好一個HOC之后,我們使用的時候就簡單多了,還看最開始的例子,我們就不需要寫 componentWillMount 了。

import React, { Component } from 'react'
import withStorage from '@/utils/withStorage'

class Page1 extends Component{
  render() {
    return <h2>{this.props.data}</h2>
  }
}

export default withStorage(Page1)

很明顯,這是一個裝飾器模式,那么就可以使用ES7形式

import React, { Component } from 'react'
import withStorage from '@/utils/withStorage'

@withStorage
class Page1 extends Component{
  render() {
    return <h2>{this.props.data}</h2>
  }
}

export default Page1

歡迎大家補充!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容