react摘要

基礎(chǔ)

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

jsx表達(dá)式的用法

  1. 可以放JS的執(zhí)行結(jié)果
  2. 如果換行需要用()包裹jsx代碼
  3. 可以把JSX元素當(dāng)作函數(shù)的返回值
  4. <{來判斷是表達(dá)式還是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)系作者
  • 序言:七十年代末是偷,一起剝皮案震驚了整個濱河市拳氢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蛋铆,老刑警劉巖馋评,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異刺啦,居然都是意外死亡留特,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進店門玛瘸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蜕青,“玉大人,你說我怎么就攤上這事糊渊∮液耍” “怎么了?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵渺绒,是天一觀的道長贺喝。 經(jīng)常有香客問我,道長宗兼,這世上最難降的妖魔是什么躏鱼? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮殷绍,結(jié)果婚禮上染苛,老公的妹妹穿的比我還像新娘。我一直安慰自己篡帕,他們只是感情好殖侵,可當(dāng)我...
    茶點故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著镰烧,像睡著了一般拢军。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上怔鳖,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天茉唉,我揣著相機與錄音,去河邊找鬼结执。 笑死度陆,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的献幔。 我是一名探鬼主播懂傀,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蜡感!你這毒婦竟也來了蹬蚁?” 一聲冷哼從身側(cè)響起恃泪,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎犀斋,沒想到半個月后贝乎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡叽粹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年览效,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片虫几。...
    茶點故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡锤灿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出持钉,到底是詐尸還是另有隱情衡招,我是刑警寧澤,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布每强,位于F島的核電站,受9級特大地震影響州刽,放射性物質(zhì)發(fā)生泄漏空执。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一穗椅、第九天 我趴在偏房一處隱蔽的房頂上張望辨绊。 院中可真熱鬧,春花似錦匹表、人聲如沸门坷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽默蚌。三九已至,卻和暖如春苇羡,著一層夾襖步出監(jiān)牢的瞬間绸吸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工设江, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留锦茁,地道東北人。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓叉存,卻偏偏與公主長得像码俩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子歼捏,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,747評論 2 361

推薦閱讀更多精彩內(nèi)容