React專題:不可變屬性

本文是『horseshoe·React專題』系列文章之一,後續會有更多專題推出javascript

來個人 GitHub repo 閱讀完整的專題文章vue

來個人 我的博客 得到無與倫比的閱讀體驗java

React是用來解決狀態同步的,但它卻有一個與this.state並駕齊驅的概念。react

這就是this.propsgit

this.props是組件之間溝通的一個接口。github

原則上來說,它只能從父組件流向子組件,可是開發者有各類hack技巧,基本上近親之間溝通是不成問題的。編程

this.props

this.props是一個極其簡單的接口。世界須要更多這樣的傻瓜接口

你只須要像寫HTML標籤的屬性同樣,把它寫上去,它就傳到了子組件的this.props裏面。redux

不過有幾個地方須要注意:框架

  • 有兩個特殊的屬性refkey,它們各有用途,並不會傳給子組件的this.props
  • 若是隻給屬性不給值,React會默認解析成布爾值true
  • 除了字符串,其餘值都要用花括號包裹。
  • 若是你把屬性給了標籤而不是子組件,React並不會解析。
import React, { Component, createRef } from 'react';
import Child from './Child';

class App extends Component {
    isPopular = false;
    refNode = createRef();
    
    render() {
        return [
            <Child key="react" ref={this.refNode} isPopular />,
            <Child key="vue" url="https://github.com/vuejs/vue" star={96500} />,
            <Child key="angular" owner="google" isPopular={this.isPopular} />,
        ];
    }
}

export default App;
複製代碼

this.props是一個不可變對象

React具備濃重的函數式編程的思想。ide

提到函數式編程就要提一個概念:純函數。

純函數有幾個特色:

  • 給定相同的輸入,老是返回相同的輸出。
  • 過程沒有反作用。
  • 不依賴外部狀態。
function doSomething(a, b) {
    return a + b;
}
複製代碼

這是一種編程思想。若是你對這個概念有點模糊,我能夠舉個例子:

你的殺父仇人十年後忽然現身,因而你決定僱傭一個冷麪殺手去解決他。

你會找一個什麼樣的殺手呢?

  • 給多少錢辦多少事,效果可預期,從不失手。
  • 不誤傷百姓,不引發動靜。
  • 沒有團伙,單獨做案,乾淨利落,便於封口。

若是你面對殺父仇人有這樣的覺悟,那麼純函數即是你的囊中之物了。

爲何要提純函數?由於this.props就是汲取了純函數的思想。

它最大的特色就是不可變。

this.state不同的是,this.props來真的。雖然this.state也反對開發者直接改變它的屬性,但畢竟只是嘴上說說,仍是要靠開發者本身的約束。然而this.props會直接讓你的程序崩潰。

加上React也沒有this.setProps方法,因此不須要開發者自我約束,this.props就是不可變的。

溝通基本靠吼

父組件給子組件傳值

這個無需贅言,最直觀的傳值方式。

import React from 'react';
import Child from './Child';

const App = () => {
    return (
        <Child star={1000} /> ); } export default App; 複製代碼

子組件給父組件傳值

其實就是利用回調函數的參數傳遞值。

父組件定義一個方法,將該方法經過props傳給子組件,子組件須要給父組件傳值時,便傳參執行該方法。因爲方法定義在父組件裏,父組件能夠接收到該值。

import React, { Component } from 'react';
import Child from './Child';

class App extends Component {
    state = { value: '' };
    
    render() {
        return (
            <Child handleSomething={this.handleSomething} /> ); } handleSomething = (e) => { this.setState({ value: e.target.value }); } } export default App; 複製代碼
import React from 'react';

const Child = (props) => {
    return (
        <input type="text" onChange={props.handleSomething} /> ); } export default Child; 複製代碼

兄弟組件之間傳值

原理和回調函數同樣,只不過這裏父組件只是一個橋樑。

父組件接收到回調函數的值之後,經過this.setState保存該值,並觸發另外一個子組件從新渲染,從新渲染後另外一個子組件即可以得到該值。

import React, { Component, Fragment } from 'react';
import ChildA from './ChildA';
import ChildB from './ChildB';

class App extends Component {
    state = { value: '' };
    
    render() {
        return (
            <Fragment>
                <ChildA handleSomething={this.handleSomething} />
                <ChildA value={this.state.value} />
            </Fragment>
        );
    }
    
    handleSomething = (e) => {
        this.setState({ value: e.target.value });
    }
}

export default App;
複製代碼
import React from 'react';

const ChildA = (props) => {
    return (
        <input type="text" onChange={props.handleSomething} /> ); } export default ChildA; 複製代碼
import React from 'react';

const ChildB = (props) => {
    return (
        <div>{props.value}</div>
    );
}

export default ChildB;
複製代碼

createContext

👽這是React v16.3.0發佈的API。

React爲開發者提供了一扇傳送門,它就是Context對象。

嚴格來講,Context早就存在於React中了,不過一直以來都不是正式的API。

終於在v16.3.0轉正了。

爲何說Context是一扇傳送門?由於它能夠跨組件傳遞數據。不是父子之間的小打小鬧哦,而是能夠跨任意層級。可是有一個限制,數據只能向下傳遞,緣由就是後面要講到的單向數據流。

開發者經過createContext建立一個上下文對象(React特別喜歡create),而後找一個頂級組件做爲Provider。接下來就能夠在任意下級組件消費它提供的數據了。

  • 只要Provider的數據改變,就會觸發Consumer的更新。
  • 建立時能夠提供一個默認值,另外掛載時能夠經過value屬性傳遞數據。可是默認值只有在不提供Provider的狀況下才起做用。
  • 開發者能夠建立多個Context。
  • Consumer的children必須是一個函數。

舊的Context存在一個問題,若是接收組件的shouldComponentUpdate生命週期鉤子返回false,則它不會接收到Context中的數據,由於它是經過this.props一級一級往下傳的。

而新的Context採起的是訂閱發佈模式,因此不存在這個問題。

實際上react-redux庫的Provider組件內部就是使用了舊的Context API,不過redux作了一些優化。

import { createContext } from 'react';

const { Provider, Consumer } = createContext({ lang: 'en' });

export { Provider, Consumer };
複製代碼
import React, { Component } from 'react';
import { Provider } from './context';
import Todos from './Todos';

const App = () => {
    return (
        <Provider value={{ lang: 'zh' }}> <Todos /> </Provider>
    );
}

export default App;
複製代碼
import React, { Fragment } from 'react';
import TodoItem from './TodoItem';

const Todos = () => {
    return (
        <Fragment> <TodoItem /> <TodoItem /> <TodoItem /> </Fragment>
    );
}

export default Todos;
複製代碼
import React from 'react';
import { Consumer } from './context';

const TodoItem = () => {
    return (
        <Consumer> {({ lang }) => <div>{lang === 'en' ? 'todo' : '要作'}</div>} </Consumer>
    );
}

export default TodoItem;
複製代碼

單向數據流

水往低處流,這是天然規律。

React經過描述狀態來控制UI的表達,這就涉及到UI的更新機制。

狀態除了內部狀態以外,確定有一些狀態是要組件之間共享的,因此,一旦一個組件的狀態更新了,可能會牽扯到不少組件的更新,框架的更新機制必將變的異常複雜。

可是迴歸到水的意象,若是狀態的流向是單向的,並且是自上往下流動,這就變的很是符合直覺,並且更新機制能夠作到極簡:我更新,則個人全部下級也更新。

這就是this.props的思想源頭。

它雖然叫props,但它也是狀態,只不過是共享的狀態。

它只能自頂向下流動。

內部不能改變this.props

某個props的源頭更新了,則流經的全部組件都要更新,除非開發者手動禁止。

脈絡清晰,this.props纔是賦予了React血液的東西。

關於React摒棄了表單雙向數據綁定的問題,它只是想把單向數據流作的更完全一點。其實表單的狀態,歸根結底是組件內部的狀態,跟單向數據流無關。

什麼是雙向數據綁定?就是表單輸入,與之綁定的變量自動獲取到輸入的值,變量的值改變,與之綁定的表單的值隨即改變,兩種流向都自動綁定了。

但其實雙向數據綁定不就是value的單向綁定加onChange事件監聽麼!React也能夠經過兩步作到。

總結:雙向數據綁定不影響單向數據流,React也能夠實現雙向的同步。

React專題一覽

什麼是UI

JSX

可變狀態

不可變屬性

生命週期

組件

事件

操做DOM

抽象UI

相關文章
相關標籤/搜索