React我的入門總結《二》

上次總結的知識已經入門的差很少了,今天繼續總結第二階段的東西,若是發覺有錯請及時幫我糾正一下謝謝!

前端應用狀態管理 —— 狀態提高

React組件中父組件能夠經過<Children state={this.state} />這種方式傳遞狀態,而後在子組件裏面能夠經過this.props拿到這個狀態,可是兩個子組件若是須要傳遞狀態的話,子組件之間是沒法直接訪問獲得的。css

咱們能夠將這種組件之間共享的狀態交給組件最近的公共父節點保管,而後再經過props傳遞給另外一個子組件就好了,這樣就能夠在子組件之間共享數據了,把狀態交給公共父組件的行爲就叫狀態提高,當某個狀態被多個組件依賴或者影響的時候,就把該狀態提高到這些組件的最近公共父組件中去管理,用 props 傳遞數據或者函數來管理這種依賴或行爲。html

在父組件中傳遞一個回調函數而後子組件調用把東西傳過來,而後父組件再傳遞給其餘子組件,咱們就能夠實現這種效果了。前端

<!-- index -->
class Index extends Component {
    constructor(props) {
        super(props)
        this.state = {
            message: ''
        }
    }
    
    <!-- 回調函數 -->
    onSubString(options) {
        <!-- 回調結束設置參數 -->
        console.log(options)
        this.setState({
            message: options
        })
    }

    render() {
        return(
            <div>
                <!-- 向組件一傳入一個回調函數 -->
                <Template1 onSubString={this.onSubString.bind(this)} />
                <!-- 向組件二傳入組件一的參數 -->
                <Template2 message={this.state.message} />
            </div>
        )
    }
}

<!-- template1 -->
class Template1 extends Component {
    static defaultProps = {
        onSubString() {
            console.log('默認值')
        }
    }

    constructor(props) {
        super(props)
        this.state = {
            message: 'is template1'
        }
    }
    
    <!-- 組件掛載前調用回調函數傳入參數 -->
    componentWillMount() {
        this.props.onSubString(this.state.message)
    }

    render() {
        return (
           <div className="template1">
                <h1>template1</h1>
           </div> 
        )
    }
}

<!-- template2 -->
class Template2 extends Component {
    constructor(options) {
        super(options)
    }

    render() {
        return(
            <div className="template2">
                <!-- 接受組件一的參數 -->
                <h1>這裏是template2 收到 {this.props.message}</h1>
            </div>
        )
    }
}

<!-- 渲染 -->
ReactDOM.render(
    <Index />,
    document.getElementById('root')
)
    
複製代碼

上面是打開瀏覽器展現的效果,狀態提高項對於複雜的項目來講很很差維護和管理,對於子組件之間傳遞參數通常都是利用Reudx來操做,也就是把一組數據共享出來全部組件均可以拿到。api

React 生命週期

對於各類具備架構能力的框架來講生命週期是少不了的,React的生命週期跟其餘框架的也差很少,首先得看看React的運行過程。數組

ReactDOM.render(
 <Index />, 
  document.getElementById('root')
)

編譯爲:

ReactDOM.render(
  React.createElement(Index, null), 
  document.getElementById('root')
)

複製代碼

執行上面步驟時React也會執行幾個步驟:瀏覽器

// React.createElement 中實例化一個 Index
    const Index = new Index(props, children)
    
    // React.createElement 中調用 index.render 方法渲染組件的內容
    const indexJsxObject = index.render()
    
    // ReactDOM 用渲染後的 JavaScript 對象來來構建真正的 DOM 元素
    const indexDOM = createDOMFromObject(indexJsxObject)
    
    // ReactDOM 把 DOM 元素塞到頁面上
    document.getElementById('root').appendChild(indexDOM)
複製代碼

咱們把 React.js 將組件渲染,而且構造 DOM 元素而後塞入頁面的過程稱爲 組件的掛載安全

-> constructor() // 初始化
    -> componentWillMount() // 掛載前
    -> render() // 渲染
    // 而後構造 DOM 元素插入頁面
    -> componentDidMount() // 掛載後
    // ...
    // 即將從頁面中刪除
    -> componentWillUnmount() // 刪除
    // 從頁面中刪除
複製代碼

除了上面掛載和刪除的生命週期還有更新的生命週期,更新的生命週期。咱們知道setData和組件的props會給組件和另外一個組件帶來從新渲染,從而會觸發更新生命週期性能優化

1. componentWillUpdate() // 更新前
    2. componentDidUpdate() // 更新後
    3. componentWillReceiveProps() // 組件從父組件接收到新的 props 以前調用
複製代碼

除了上面三個之外還有一個極爲關鍵的 shouldComponentUpdate(),這個方法能夠控制組件是否從新渲染。若是返回 false 組件就不會從新渲染。這個生命週期在 React.js 性能優化上很是有用。bash

class Index extends Component {
    constructor(props) {
        super(props)
        this.state = {
            message: '組件更新'
        }
    }

    updateMessage() {
        this.setState({
            message: this.state.message
        })
    }
    
    componentWillUpdate() {
        console.log('更新前')
    }

    componentDidUpdate() {
        console.log('更新後')
    }

    render() {
        return(
            <div className="header">
                <button onClick={this.updateMessage.bind(this)}>更新數據</button>
            </div>
        )
    }
}
複製代碼

上面的代碼我點擊三次而且我只是調用了setState並無更新message的數據,他也會每次都從新渲染,爲了節約性能,咱們可使用shouldComponentUpdate()方法,shouldComponentUpdate()是重渲染時render()函數調用前被調用的函數,它接受兩個參數:nextPropsnextState,分別表示下一個props和下一個state的值。而且,當函數返回false時候,阻止接下來的render()函數的調用,阻止組件重渲染,而返回true時,組件照常重渲染。架構

class Index extends Component {
    constructor(props) {
        super(props)
        this.state = {
            message: '組件更新'
        }
    }

    updateMessage() {
        this.setState({
            message: this.state.message
        })
    }
    <!-- 調用 setState 時控制是否須要從新渲染 -->
    shouldComponentUpdate(nextProps, nextState) {
        console.log(nextState.message) // 打印的是更新前的 message
        console.log(nextProps) // 打印的是更新前的 props (這裏沒有 props 因此是空的對象)
        <!-- 利用上一次的 message 跟更新後的對比 若是相同則返回 false -->
        if (nextState.message == this.state.message) {
            return false
        }
    }

    componentWillUpdate() {
        console.log('更新前')
    }

    componentDidUpdate() {
        console.log('更新後')
    }

    render() {
        return (
            <div className="header">
                <button onClick={this.updateMessage.bind(this)}>更新數據</button>
            </div>
        )
    }
}
複製代碼

我這裏點擊了4次,可是沒有打印其餘兩個更新的生命週期,只打印了我在shouldComponentUpdate()方法裏打印的東西,這說明頁面並無從新渲染。

咱們再繼續看props的從新渲染。組件的 state 沒有變化,而且從父組件接受的 props 也沒有變化,也有可能從新渲染。

<!-- Son -->
class Son extends Component {
    <!-- 點擊時候把父級傳過來的參數原封不動傳回去 -->
    updateMessage() {
        this.props.handleClick(this.props.message)
    }

    render() {
        <!-- 看看是否從新渲染 -->
        console.log(this.props.message, 'son')
        return(
            <div>
                <button onClick={this.updateMessage.bind(this)}>{this.props.message}</button>
            </div>
        )
    }
}

<!-- Index -->

class Index extends Component {
    constructor(props) {
        super(props)
        this.state = {
            message: '組件更新',
        }
    }

    updateMessage() {
        this.setState({
            message: this.state.message
        })
    }

    componentWillUpdate() {
         console.log('更新前')
    }

    componentDidUpdate() {
        console.log('更新後')
    }
    
    <!-- 把子級的參數從新設置 -->
    handleClick(message) {
        this.setState({
            message: message
        })
    }

    render() {
        return (
            <div className="header">
                <Son handleClick={this.handleClick.bind(this)} message={this.state.message} />
            </div>
        )
    }
}
複製代碼

我這裏點擊三次,不只父組件會從新渲染,連子組件都會從新渲染。咱們再來處理一下這個狀況。

<!-- Son -->
shouldComponentUpdate(nextProps, nextState) {
    console.log(nextProps.message)
    if (nextProps.message === this.props.message) {
        return false
    }
}
複製代碼

若是在子組件加上這個生命週期,監聽父級上次傳過來的props是否和此次穿過來的props是否同樣,而後咱們再點擊時子組件就不會從新渲染了。

這裏進來時渲染打印一次,而後以後就沒有打印了,以後點的兩次只打印父級的更新生命週期還有子級shouldComponentUpdate()裏面打印的props

React 中的 DOM 操做

通常來講相似VueReact這種具備MVVM思想的框架通常能作到像setData同樣的從新渲染,還有父子組件傳參來更新狀態,這些都不須要經過手動操做DOM來實現,雖然這些從新渲染機制幫助咱們免除大部分的DOM操做,可是有部分仍是不能知足,最多見的就是獲取元素的寬度和高度來進行操做,或者是控制輸入框的狀態等等,這些都是須要依靠DOM操做來實現的。

React提供了ref屬性來幫助咱們獲取 已經掛載 的元素的DOM節點

class Index extends Component {
    componentDidMount() {
        console.log(this.indexBox)
    }

    render() {
        return(
            <div className="index" ref={(indexBox) => this.indexBox = indexBox} style={{width: '500px', height: '500px', backgroundColor: 'green'}}></div>
        )
    }
}
複製代碼

獲取到元素以後你能夠調用他原有的api,好比輸入框的禁止輸入以及自動聚焦等等操做。

能不用 ref 就不用。特別是要避免用 ref 來作 React.js 原本就能夠幫助你作到的頁面自動更新的操做和事件監聽。多餘的 DOM 操做實際上是代碼裏面的「噪音」,不利於咱們理解和維護。

除此以外咱們還能夠給組件加上ref

class Header extends Component {
    render(){
        return(
            <div>
                <h1>組件組件組件</h1>
            </div>
        )
    }
}

class Index extends Component {
    componentDidMount() {
        console.log(this.header)
    }

    render() {
        return(
            <div>
                <Header ref={(header)=> this.header = header } />
            </div>
        )
    }
}
複製代碼

這種用法並不經常使用,也不推薦這樣作。

props.children 和容器類組件

組件自己是一個不帶任何內容的方形的容器,我能夠在用這個組件的時候給它傳入任意內容。

class Index extends Component {
    render() {
        console.log(this.props.children)
        return(
            <div className="index">
                {this.props.children}
            </div>
        )
    }
}

ReactDOM.render(
    <Index>
        <h1>我洗渣渣輝</h1>    
        <h2>我洗古天樂</h2>    
    </Index>,
    document.getElementById('root')
)
複製代碼

這個屬性打印出來的是一個數組,把咱們嵌套的jsx元素一個個都放到數組當中,而後經過props.children傳給Index,咱們能夠把它們分別存在不一樣的地方。

class Index extends Component {
    render() {
        console.log(this.props.children)
        return(
            <div className="index">
                <div className="header">
                    <!-- 第0條 -->
                    {this.props.children[0]}
                </div>
                <div className="main">
                    <!-- 第1條 -->
                    {this.props.children[1]}
                </div>
            </div>
        )
    }
}
複製代碼

使用自定義組件的時候,能夠在其中嵌套 JSX 結構。嵌套的結構在組件內部均可以經過 props.children 獲取到,這種組件編寫方式在編寫容器類型的組件當中很是有用。

dangerouslySetHTML 和 style 屬性

出於安全考慮的緣由(XSS 攻擊),在 React.js 當中全部的表達式插入的內容都會被自動轉義,就至關於 jQuery 裏面的 text() 函數同樣,任何的 HTML 格式都會被轉義掉。

class Index extends Component {
    constructor(props) {
        super(props)
        this.state = {
            content: '<h1>富文本轉義</h1>'
        }
    }

    render() {
        return(
            <div className="index">
                {this.state.content}
            </div>
        )
    }
}
複製代碼

若是要把<h1>轉爲標籤,可使用dangerouslySetHTML屬性動態設置元素的innerHTML

class Index extends Component {
    constructor(props) {
        super(props)
        this.state = {
            content: '<h1>富文本轉義</h1>'
        }
    }
    render() {
        console.log(this.props.children)
        return(
            <div className="index" dangerouslySetInnerHTML={{__html: this.state.content}}></div>
        )
    }
}
複製代碼

在添加了dangerouslySetHTML屬性的元素裏面不能再嵌套任何東西( 包括文本 ),不然將會報錯。設置 innerHTML 可能會致使跨站腳本攻擊(XSS),React.js 團隊認爲把事情搞複雜能夠防止(警示)你們濫用這個屬性。這個屬性沒必要要的狀況就不要使用。


Reactstyle裏面的css屬性須要轉化爲對象才能傳給元素,而且全部元素須要帶-的都要轉爲駝峯命名,好比font-sizebackground-color,須要寫爲fontSizebackgroundColor纔有效果。

<div className="index">
    <h1 style={{color: 'red', fontSize: 14}}>文本文本</h1>
</div>
複製代碼

PropTypes 和組件參數驗證

React.js 的組件實際上是爲了構建大型應用程序而生。可是由於 JavaScript 這樣的特性,你在編寫了一個組件之後,根本不知作別人會怎麼使用你的組件,往裏傳什麼亂七八糟的參數,咱們能夠利用propTypes這個庫來指定傳遞過來參數的類型,而這個庫在後面的context也要用到。

class Template1 extends Component {
    render() {
        return(
            <h1>{ this.props.userData.name }</h1>
        )
    }
}

class Index extends Component {

    constructor(props) {
        super(props)
        this.state = {
            userData: {
                name: 'jack',
                age: 18
            }
        }
    }
    
    render() {
        return(
            <div className="index">
                <Template1 userData={ this.state.userData } />
            </div>
        )
    }
}
複製代碼

上面的組件不管父級傳什麼東西都是沒有限制了,這讓其餘人共同開發項目很複雜,有時會由於參數問題處處找Bug,這時你能夠給組件配置參數加上 類型驗證

import PropTypes from 'prop-types'
class Template1 extends Component {
    static propTypes = {
        userData: PropTypes.object // 接受對象參數
    }
    
    render() {
        return(
            <h1>{ this.props.userData.name }</h1>
        )
    }
}

class Index extends Component {
    constructor(props) {
        super(props)
        this.state = {
            userData: {
                name: 'jack',
                age: 18
            }
        }
    }
    
    render() {
        return(
            <div className="index">
                <!-- 這裏傳遞的是數值 -->
                <Template1 userData={ 123 } /> 
            </div>
        )
    }
}
複製代碼

如今能夠驗證參數的數據類型了,若是傳的類型與設置的類型不一致,將會報出警告。

propTypes提供了多種 數據驗證 ,還能夠限制單個元素傳遞,而且當參數沒有傳遞時還能夠設置默認值,若是在不傳遞參數下還不設置默認值,則默認爲undefined,這時會出現一個很不友好的報錯信息。

這時咱們可使用 isRequired 來強調這個參數必須傳入。

static propTypes = {
    userData: PropTypes.object.isRequired
}
複製代碼

這時報錯信息裏面會再添加一條提示,這樣的話就更容易找到問題所在。


上一篇 --- React我的入門總結《一》

下一篇 --- React我的入門總結《三》

相關文章
相關標籤/搜索