react 上下文、片段及插槽

2018不下雪發表於2018-04-08

1.上下文(Context)

react是單項資料流,資料是自上往下傳遞的。如果父元件想傳遞資料給孫元件,需要父傳遞到子,子再傳遞到孫。也可以用上下文解決。

在某些場景下,你想在整個元件樹中傳遞資料,但卻不想手動地在每一層傳遞屬性。你可以直接在 React 中使用強大的”context” API解決上述問題。

react路由就是通過上下文實現的。

上下文實現步驟

1. 在父元件裡定義 childContextTypes 子上下文型別
2. 在父元件裡還要定義一個getChildContext用來返回上下文物件
3. 在要接收這些上下文物件的元件裡寫義contextTypes
複製程式碼

實現一個小功能,有header,main兩個元件,分別包含title,content元件,公用一個顏色。

父元件

  • childContextTypes
  • getChildContext

子元件

  • contextTypes
  • this.context
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';

/**
 * 1.  在父元件裡定義 childContextTypes 子上下文型別
 * 2. 在父元件裡還要定義一個getChildContext用來返回上下文物件
 * 3. 在要接收這些上下文物件的元件裡寫義contextTypes
 */
export default class HomePage extends Component {
    static childContextTypes = {
        color: PropTypes.string,
        setColor:PropTypes.func
    }
    getChildContext() {
        return {
            color: this.state.color,
            setColor:this.setColor
        }
    }
    //狀態不能別人改,只能自己改。
    constructor() {
        super();
        this.state = { color: 'red' };
    }
    setColor = (color)=>{
        this.setState({color});
    }
    render() {
        return (
            <div>
                <Header />
                <Main />
            </div>
        )
    }
}
class Header extends Component {
    render() {
        return (
            <div>
                <Title />
            </div>
        )
    }
}
class Main extends Component {
    render() {
        return (
            <div>
                <Content />
            </div>
        )
    }
}
class Title extends Component {
    static contextTypes = {
        color: PropTypes.string
    }
    render() {
        //this.context
        return (
            <div>
                <h1 style={{ color: this.context.color }}>我是標題</h1>
            </div>
        )
    }
}
class Content extends Component {
    static contextTypes = {
        color: PropTypes.string,
        setColor: PropTypes.func
    }
    render() {
        console.log(this.context);
        return (
            <div>
                <h1 style={{ color: this.context.color }}>我是內容</h1>
                <button onClick={()=>this.context.setColor('green')}>變綠</button> 
                <button onClick={()=>this.context.setColor('yellow')}>變黃</button> 
            </div>
        )
    }
}
複製程式碼

2.片段(fragments)

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

import React,{Component} from 'react';
class List extends Component{
    render(){
        return (
                { this.props.messages.map((item,key)=><li key={key}>{item}</li>)}
        )
    }
}
export default class Messages extends Component{
    constructor(){
        super();
        this.state = {messages:[1,2,3]};
    }
    render(){
        return (
            <ul>
               <List messages = {this.state.messages}/>
            </ul>
        )
    }
}
複製程式碼

將li的邏輯單獨拎出來成立一個元件,但是這樣會報錯,react只能返回一個頂層元素,現在返回的是很多li。需要在他的外層新增一個頂層元素。

class List extends Component{
    render(){
        return (
            <div>
                { this.props.messages.map((item,key)=><li key={key}>{item}</li>)}
            </div>
        )
    }
}
複製程式碼

但是這樣ul中將會有div。這樣dom結構就亂了。

這時候就需要用到片段了:

class List extends Component{
    render(){
        return (
           <React.Fragment>
                { this.props.messages.map((item,key)=><li key={key}>{item}</li>)}
           </React.Fragment>
        )
    }
}
複製程式碼

<React.Fragment>標籤不會形成新的dom元素。完美解決這個問題。

另一種更簡單更形象寫法,但是現在還不支援:

class List extends Component{
    render(){
        return (
           <>
                { this.props.messages.map((item,key)=><li key={key}>{item}</li>)}
           </>
        )
    }
}
複製程式碼

3.插槽(Portals)

Portals 提供了一種很好的方法,將子節點渲染到父元件 DOM 層次結構之外的 DOM 節點。

ReactDOM.createPortal(child, container)
複製程式碼
  • 第一個引數(child)是任何可渲染的 React 子元素,例如一個元素,字串或 片段(fragment)
  • 第二個引數(container)則是一個 DOM 元素

需求:頁面中有很多元件,但每個元件都一個個彈窗功能。彈窗不應屬於任何一個元件。

在index.html中放入模態框的根節點

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id="root"></div>
    <div id="modal-root" class="modal-root"></div>
</body>
</html>
複製程式碼

modal-root放入的為模態框元件,我們的其他元件在root中。

模態框元件

import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import './Modal.css'
class Modal extends Component {
    constructor() {
        super();
        this.container = document.querySelector('#modal-root');
    }

    render() {
        return ReactDOM.createPortal(this.props.children, this.container);
    }
}
export default class ModelPage extends Component {
    constructor() {
        super();
        this.state = {show: false};
    }

    render() {
        return (
            <div>
                <button onClick={() => this.setState({show: !this.state.show})}>顯示</button>
                {
                    this.state.show ? <Modal>
                        <div className="modal-container">
                            <div className="modal-content">
                                <h1>顯示模態視窗</h1>
                            </div>
                        </div>
                    </Modal> : null
                }
            </div>
        )
    }
}
複製程式碼

4.錯誤邊界(Error Boundaries)

部分 UI 中的 JavaScript 錯誤不應該破壞整個應用程式。 為了解決 React 使用者的這個問題,React 16引入了一個 “錯誤邊界(Error Boundaries)” 的新概念。

import React,{Component} from 'react';
class ErrorBoundary extends Component{
   constructor(){
       super();
       this.state = {hasError:false};
   }
   componentDidCatch(hasError){
    this.setState({hasError});
   }
   render(){
       if(this.state.hasError){
         return <div>此元件暫時無法顯示</div>
       }
       return this.props.children
   }
}

class Todo extends Component{
    render(){
        return <div>{null.toString()}</div>
    }
}
export default class MyPage extends Component{
    render(){
        return (
            <ErrorBoundary>
                <Todo/>
            </ErrorBoundary>
        )
    }
}
複製程式碼

null.toString()不合法,會顯示:此元件暫時無法顯示。

相關文章