React學習(5)-React中組件的數據-props

React學習(5)-React組件中的數據-props.png

前言

開發一個React應用,更多的是在編寫組件,而React組件最小的單位就是React元素,編寫組件的最大的好處,就是實現代碼的複用javascript

將一個大的應用按照功能結構等劃分紅若干個部分(組件),對每一個部分(組件)進行分開管理,與組件相關的東西放在一塊兒,達到高內聚的目的,而不一樣組件又各自獨立管理達到低耦合的效果。html

構建組件,本質上就是在編寫javascript函數,而組件中最重要的是數據,在React中數據分兩種:props和state,當定義一個組件時,它接收任意的形參(即props),並用於返回描述頁面展現內容的React元素java

不管props仍是state,當他們任何一個發生改變時,都會引起render函數的從新渲染react

一個UI組件所渲染的結果,就是經過props和state這兩個屬性在render方法裏面映射生成對應的HTML結構npm

那麼在寫一個React組件的時候,究竟何時使用state,何時使用props呢?如何的劃分組件的狀態數據?數組

那麼本節就是你想要知道的bash

React中組件的數據-props(想閱讀體驗更好,可戳該連接,內附有視頻)babel

React中的props

當經過函數聲明或者class自定義一個組件時,它會將JSX所接受的屬性(attributes)轉換爲一對象傳遞給該定義時的組件app

這個接收的對象就是props(property的簡寫),props就是組件定義屬性的集合,它是組件對外的接口,由外部經過JSX屬性傳入設置(也就是從外部傳遞給內部組件的數據)dom

一個React組件經過定義本身可以接收的prop,就定義了本身對外提供的公共接口

每一個定義的React組件應該都是獨立存在的模塊,組件以外的一切都是外部世界(組件),外部世界(組件)就是經過prop來和組件進行對話數據傳遞的

在React中,你能夠將prop相似於HTML標籤元素的屬性,不過原生HTML標籤的屬性值都是字符串,即便是內嵌js表達式,也依然是字符串,而在React中,prop的屬性值類型能夠任何數據類型(基本數據類型(number,String,null等函數)或者對象)

固然若是是非字符串數據類型,在JSX中,必需要用花括號{}把prop值給包裹起來

這也是爲何style有兩層花括號的緣由:最外層表明的是JSX語法,意味着它是一個變量對象,而內層的花括號{}表明的是一個對象

在函數聲明自定義的組件中,能夠經過props獲取組件的屬性

以下所示:自定義一個Button組件,給組件添加各個屬性值,渲染的結果以下所示

組件的props數據.png

import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';

// 函數式組件,定義一個Button組件,首字母大寫
function Button(props) {
  console.log(props); // 將會把調用處組件的style屬性給打印出來
  const btnStyles = {
  width: props.style.width,
  height: props.style.height,
  background: props.style.background,
  color: props.style.color,
  border: props.style.border,
  outline: props.style.outline,
  cursor: props.style.cursor
};
return (
  <div>
    <button style = { btnStyles }>按鈕</button>
  </div>
);
}

const btnStyle = {
  width: "100px",
  height: "40px",
  background: "orange",
  color: "#fff",
  border: "none",
  outline: "none",
  cursor: "pointer"
}
const container = document.getElementById('root');

ReactDOM.render(<Button style = { btnStyle } />, container);
複製代碼

類class聲明的組件: 經過Es6中的class聲明,繼承React.Component進行實現

import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';

// 類組件,經過class關鍵字聲明使用
class Button extends Component {
  constructor(props){
  super(props);

}

render() {
  console.log(this.props);
  // 這裏利用Es6中的解構賦值
  const { width, height, background, color, border, outline,cursor} = this.props.style;
  const btnStyles = {
    width,  // 等於width:width
    height,
    background,
    color,
    border,
    outline,
    cursor
}
return (
<div>
   <button style = { btnStyles }>按鈕</button>
</div>
);
}
}
// 該Button組件按鈕自身擁有的屬性
const btnStyle = {
  width: "100px",
  height: "40px",
  background: "orange",
  color: "#fff",
  border: "none",
  outline: "none",
  cursor: "pointer"
}

const container = document.getElementById('root');

ReactDOM.render(<Button style = { btnStyle } />, container);
複製代碼

上述代碼中分別使用了函數式組件與類聲明的組件,在調用組件時,對組件設置了props值,而在組件內部經過this.props獲取屬性值

從而得出,父組件(外部組件)向子(內)組件傳值是經過設置JSX屬性的方式實現的,而在子組件內部獲取父(外部)組件數據是經過this.props來獲取的,也能夠這麼認爲,props就是對外提供的數據接口

對於用類class聲明的組件,讀取prop的值,是經過this.props來獲取的

首先用construcor定義了一個構造函數,而且給它接收了一個props形參,而後在constructor構造器函數內調用super(props)

這個是固定的寫法,組件繼承父類的一些方法,若是一個組件須要定義本身的構造函數,那麼就必定要調用super(props),也就是繼承了React.Component構造函數

至於爲何要調用super(props)方法,由於Es6採用的是先建立父類實例的this,而後在用子類的構造函數修改this

若是沒有constructor構造器函數,調用super(),以及參數props,它是會報錯的

在組件實例被構造以後,該組件的全部成員函數都沒法經過this.props訪問到父組件傳遞過來的props值,錯誤以下所示

ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
複製代碼

關於constructor()構造器函數

這個constructor(props)構造器函數是自動就生成的,若是沒有聲明,React會默認添加一個空的construcor,而且會自動執行,有且只執行一次,能夠將它視爲鉤子函數(生命週期函數)

這個constructor函數接收props形參數,接收外部組件傳值的集合,只要組件內部要使用prop值,那麼這個props參數是要必傳的,不然的話在當前組件內就沒法使用this.props接收外部組件傳來的值

可是不管有沒有constructor函數,render函數,子組件內均可以使用this.props獲取組件外部的數據,它是默認自帶的

constructor(props){
   super(props);
}
複製代碼

至於寫不寫構造器函數,若是該自定義的組件不須要初始化state,不用進行方法的綁定(this壞境的設置),只是單純的用於接收外部組件傳來的props數據用做展現,並無UI交互渲染動做

那麼就不須要爲該React組件實現構造函數

若是是這樣,則更應該把它轉換爲函數式(無狀態UI)組件,由於它的效能是最高的

不然的話,那麼就要編寫constructor構造器函數,何況Es6編寫類的方式提供了更多實用的功能,特定的條件下,該用仍是要用的

通常而言,在React中,構造函數僅用於下面兩種狀況:

  • 經過給this.state賦值對象來初始化當前組件內部的state(狀態)

  • 在JSX中監聽綁定事件處理函數(this壞境的綁定)

在constructor()函數中不要調用setState()方法,若是組件須要使用內部狀態state,直接在構造函數中爲this.state賦初始state值

constructor(props){
  super(props);

  // 不要在這裏調用this.setState(),更改state狀態數據
  this.state = {
   // 屬性:屬性值
   count: 0
  }
  //this.方法 = this.方法.bind(this);
   this.handleClick = this.handleClick.bind(this)
}
複製代碼

只能在構造函數中直接爲this.state賦值,若是在其餘地方法須要改變該state的值,應該使用this.setState()方法替代

注意:

若是把函數組件替換成類組件的寫法,在子組件內部接收外部的props值時,須要將props更改爲this.props的寫法,反過來也是,類聲明的組件替換成函數式(無狀態)組件時,須要將this.props替換成props

而在用class類定義的組件時,一旦對組件初始化設置完成,該組件的屬性就能夠經過this.props獲取獲得,而這個this.props是不可更改的

不要輕易更改設置this.props裏面的值,換句話說,組件的props屬性只具有可讀性,不能修改自身的props,這不區分是用函數聲明的組件仍是用class聲明的組件,沒法直接的更改props值

以下所示:點擊按鈕,想要改變外部傳進去的props值,在代碼中直接更改props值,是會報錯的以下圖錯誤所示:

import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';

// 類組件
class Button extends Component {
  constructor(props){
   super(props);

  }

render() {
  const { width, height, background, color, border, outline,cursor} = this.props.style;
  const btnStyles = {
    width,
    height,
    background,
    color,
    border,
    outline,
    cursor
}
return (
  <div>
    <button onClick = { this.handleBtnClick.bind(this) } style = { btnStyles }>{ this.props.btnContent }</button>
  </div>
);
}

handleBtnClick(){
   // 直接更改props的值,是會報錯的,在React中不容許這麼作
   this.props.btnContent = "按鈕B";
}
}

const btnStyle = {
  width: "100px",
  height: "40px",
  background: "orange",
  color: "#fff",
  border: "none",
  outline: "none",
  cursor: "pointer"
}
const container = document.getElementById('root');

ReactDOM.render(<Button btnContent ="按鈕A" style = { btnStyle } />, container);
複製代碼

沒法更改props的值.png
由於在React中,數據流是單向的,不能改變一個組件被渲染時傳進來的props

之因此這麼規定,由於組件的複用性,一個組件可能在各個頁面上進行復用,若是容許被修改的話,這個組件的顯示形態會變得不可預測,當組件出現某些bug的時候,會給開發者帶來困擾,調試將會是噩夢,沒法定位,違背組件的設計原則了

可是這並不表明着props的值並不能被修改,有時候,因爲業務的需求,咱們是須要對props值進行修改的

若是想要修改,那麼能夠經過藉助React內置的一個方法setState方法從新渲染的方式,把props傳入組件當中,這樣的話,由props屬性決定這個組件的顯示形態也會獲得相應的改變

更改以下所示:

import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';

// 類組件
class Button extends Component {
  constructor(props){
    super(props);
    // state是組件內部的狀態
    this.state = {
       btnContent: this.props.btnContent
    }

}

render() {
const { width, height, background, color, border, outline,cursor} = this.props.style;
const btnStyles = {
  width,
  height,
  background,
  color,
  border,
  outline,
  cursor
}
return (
   <div>
     <button onClick = { this.handleBtnClick.bind(this) } style = { btnStyles }>{ this.state.btnContent }</button>
   </div>
);
}

handleBtnClick(){
   // this.props.btnContent = "按鈕B";
   this.setState({
     btnContent: "按鈕B"
   });
}
}

const btnStyle = {
  width: "100px",
  height: "40px",
  background: "orange",
  color: "#fff",
  border: "none",
  outline: "none",
  cursor: "pointer"
}


const container = document.getElementById('root');

ReactDOM.render(<Button btnContent ="按鈕A" style = { btnStyle } />, container);
複製代碼

利用setState更改props.gif

關於React中事件監聽this的綁定

this的指向一般與它的執行上下文有關係,通常有如下幾種方式

  • 函數的調用方式影響this的取值,若是做爲函數調用,在非嚴格模式下,this指向全局window對象,在嚴格模式(use "strict")下,this指向undefined

  • 若是做爲方法的調用,this指向調用的對象,誰調用它,this就指向誰

  • 做爲構造器函數調用,this指向該建立的實例化對象(類實例方法裏面的this都指向這個實例自己)

  • 經過call,apply調用,this指向call和apply的第一個參數

在React中,給JSX元素,監聽綁定一個事件時,你須要手動的綁定this,若是你不進行手動bind的綁定,this會是undefined,在Es6中,用class類建立的React組件並不會自動的給組件綁定this到當前的實例對象上

將該組件實例的方法進行this壞境綁定是React經常使用手段

代碼以下所示:

import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';

// 類組件
class Button extends Component {
  constructor(props){
  super(props);

  // this壞境的綁定,這是React裏面的一個優化,constructor函數只執行一次
  this.handleBtnClick = this.handleBtnClick.bind(this);

  this.state = {
    btnContent: this.props.btnContent
  }

}



render() {
   const { width, height, background, color, border, outline,cursor} = this.props.style;
   const btnStyles = {
       width,
       height,
       background,
       color,
       border,
       outline,
       cursor
}
return (
  <div>
    <button onClick = { this.handleBtnClick } style = { btnStyles }>{ this.state.btnContent }</button>
  </div>
);
}

handleBtnClick(){
  // this.props.btnContent = "按鈕B";
  this.setState({
     btnContent: "按鈕B"
  });
}
}

const btnStyle = {
  width: "100px",
  height: "40px",
  background: "orange",
  color: "#fff",
  border: "none",
  outline: "none",
  cursor: "pointer"
}


const container = document.getElementById('root');

ReactDOM.render(<Button btnContent ="按鈕A" style = { btnStyle } />, container);
複製代碼

固然若是不用這種手動綁定this的方式,用箭頭函數也是能夠的,箭頭函數沒有this的綁定,以下代碼所示

import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';

// 類組件
class Button extends Component {
   constructor(props){
   super(props);

   // this壞境的綁定,這是React裏面的一個優化,constructor函數只執行一次
   // this.handleBtnClick = this.handleBtnClick.bind(this);
   this.state = {
     btnContent: this.props.btnContent
   }

}



render() {
const { width, height, background, color, border, outline,cursor} = this.props.style;
const btnStyles = {
  width,
  height,
  background,
  color,
  border,
  outline,
  cursor
}
return (
  <div>
    <button onClick = { () => { this.handleBtnClick() } } style = { btnStyles }>{ this.state.btnContent }</button>
   <!--或者如下寫法-->
   <!--<button onClick = { this.handleBtnClick } style = { btnStyles }>{ this.state.btnContent }</button>-->
  </div>
);
}

handleBtnClick(){
  // this.props.btnContent = "按鈕B";
  this.setState({
    btnContent: "按鈕B"
  });
}
// handleBtnClick = () => {
// this.setState({
// btnContent: "按鈕B"
// });
// }

}



const btnStyle = {
  width: "100px",
  height: "40px",
  background: "orange",
  color: "#fff",
  border: "none",
  outline: "none",
  cursor: "pointer"
}


const container = document.getElementById('root');

ReactDOM.render(<Button btnContent ="按鈕A" style = { btnStyle } />, container);
複製代碼

對比兩種實現方式,都是能夠的,可是官方推薦使用bind綁定,使用bind不只能夠幫咱們把事件監聽方法中的this綁定到當前的組件實例上

bind後面還還能夠設置第二個參數,把與組件相關的東西傳給組件的,並在construcor構造器函數中進行初始化綁定,雖然bind的使用會建立一個新的函數,可是它在constructor中只會調用一次

而利用箭頭函數,箭頭函數中沒有this的綁定,從性能上講,它是會重複調用,進行額外的渲染,不如在構造器函數中進行this壞境的初始化手動綁定

在上面說到了prop值既然能夠是任意數據類型,正好利用這一特性,子組件接收父組件用this.props能夠獲取屬性,那麼這個屬性值能夠是個方法,子組件也能夠調用父組件的方法,來達到子組件向父組件傳遞數據

以下代碼所示,最終的效果以下所示

子組件向父組件傳遞內容.gif

import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';

// 定義一個父組件
class ParentComponent extends Component {
  constructor(props){
  super(props);

  console.log("父組件props",props);

}

childContent(parm) {
  alert(parm);
}

render(){
return (
  <Fragment>
     <div>{ this.props.parentContent }</div>
     <ChildComponent getChildContent = { this.childContent } childcon = "我是子組件的內容" ></ChildComponent>
  </Fragment>
);
}
}
// 定義子組件
class ChildComponent extends Component {
  constructor(props){
  super(props);
  console.log("子組件props",props);

}

handleChild = ()=> {
  const {getChildContent, childcon} = this.props;
  getChildContent(childcon);
}

render(){
return (
  <Fragment>
    <div onClick = { this.handleChild }>{ this.props.childcon}</div>
  </Fragment>
);
}
}

const container = document.getElementById('root');

ReactDOM.render(<ParentComponent parentContent = "我是父組件的內容" />, container);
複製代碼

從上面的代碼中,能夠看得出,父組件中JSX的prop值能夠是一個方法,在子組件想要把數據傳遞給父組件時,須要在子組件中調用父組件的方法,從而達到了子組件向父組件傳遞數據的形式

這種間接操做的方式在React中很是重要.固然你看到上面把子組件與父組件放在一個文件當中,或許看得不是很舒服,你能夠把子組件單獨的抽離出去,經過Es6中的export,import導出導入的方式是能夠的(後面每每用的是這種方式)

在index.js同級目錄下建立一個ChildComponent.js的文件

import React, { Component, Fragment} from 'react';

class ChildComponent extends Component {
  constructor(props){
  super(props);
  console.log("子組件props",props);

}

handleChild = ()=> {
  const {getChildContent, childcon} = this.props;
  getChildContent(childcon);
}

render(){
return (
  <Fragment>
   <div onClick = { this.handleChild }>{ this.props.childcon}</div>
  </Fragment>
);
}
}

export default ChildComponent;
複製代碼

在index.js中,經過import將ChildComponent組件進行引入,以下代碼所示

import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
import ChildComponent from './ChildComponent'; // 引入ChildComponent組件


// 定義一個父組件
class ParentComponent extends Component {
  constructor(props){
  super(props);

  console.log("父組件props",props);

}

childContent(parm) {
  alert(parm);
}

render(){
return (
  <Fragment>
   <div>{ this.props.parentContent }</div>
     <ChildComponent getChildContent = { this.childContent } childcon = "我是子組件的內容" ></ChildComponent>
  </Fragment>
);
}
}
const container = document.getElementById('root');

ReactDOM.render(<ParentComponent parentContent = "我是父組件的內容" />, container);
複製代碼

使用PropTypes進行類型檢查

既然prop是組件對外的接口,那麼這個接口就必然要符合必定的數據規範,換句話說:也就是輸入與輸出的類型要保持一致,不然的話就會出問題

經過類型檢查捕獲一些錯誤,規避一些程序上的bug,React內置了一些類型檢查的功能,要在組件的props上進行類型的檢查,只須要作一些特定的propTypes屬性配置便可

定義一個組件,爲了該程序的嚴謹性,應該規範組件數據的以下方面

  • 這個組件支持哪些prop

  • 每一個prop應該是什麼樣的格式

在React中,藉助了第三方庫prop-types來解決這一問題,經過PropTypes來支持這一功能

命令行終端下,安裝prop-types這個庫

cnpm install --save prop-types
複製代碼

在你所要驗證的組件內,引入prop-types庫

import PropTypes from 'prop-types'

class PropTest extends Component {

render(){
return (
  <Fragment>
   <div>{ this.props.propContent }</div>
  </Fragment>
);
}
}
// 類組件.propTypes對象下進行配置
PropTest.propTypes = {
   propContent: PropTypes.number
}

const container = document.getElementById('root');

ReactDOM.render(<PropTest propContent = "我是prop屬性內容" />, container);
複製代碼

控制檯錯誤顯示以下:

prop類型的校驗.png
錯誤的信息是:提供給PropTest的類型是string的proppropContent,但指望的是number

具體的解決辦法就是:要麼更改傳入屬性值的prop類型,要麼把校驗類型進行更改與之對應的

PropType提供了一系列的驗證方法,用於確保組件接收到的數據類型是有效準確的,一旦傳入的prop值類型不正確時,控制檯將會顯示的警告,雖然程序不會報錯,可是會出現警告.

有時候,對於外部傳入組件內部的prop值,不管有沒有傳入,爲了程序的健壯性,,須要判斷prop值是否存在,咱們每每須要設置一個初始默認值,若是不存在,就給一個默認初始值,固然你利用傳入的prop進行「||」或字符進行處理也是能夠的

在React中,能夠配置defaultProps進行默認prop值的設置,代碼以下所示

具體寫法:

組件.defaultProps = {
 prop屬性名稱: 默認值
}
複製代碼
import React, { Fragment, Component } from "react";
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';

class PropTest extends Component {

render(){
return (
  <Fragment>
   <div>{ this.props.propContent }</div>
  </Fragment>
);
}
}

PropTest.defaultProps = {
propContent: "我是propTest組件的內容"
}

const container = document.getElementById('root');
ReactDOM.render(<PropTest />, container);
複製代碼

效果以下所示

設置defaultProps.png
如上代碼,當外部組件沒有傳propContent值時,React經過defaultProps設置了一個初始默認值

它會顯示默認設置的初始值,若是外部組件傳了prop值,它會優先使用傳入的prop值,覆蓋默認設置的初始值

具體PropTypes下更多的方法,可參考官網手冊PropTypes庫的使用,也能夠查看npm中的prop-types這個庫的使用

出於性能的考慮,在開發的時候能夠發現代碼中的問題,可是放在生產壞境中就不適合了

由於它不只增長了代碼行數,佔用空間,並且還消耗CPU資源

折中的辦法就是:在開發的時候代碼定義propTypes,避免開發犯錯,但在發佈產品代碼時,用一種自動的方式將propTypes去掉,這樣在線上壞境代碼時最優的

藉助babel-plugin-transform-react-remove-prop-types這個第三方模塊進行配置處理一下的,具體詳細配置:可見npm官網對這個庫的介紹的:www.npmjs.com/package/bab…

總結

本文主要講述了React組件中的數據屬性-props,它相似HTML標籤的屬性,但屬性值能夠是任意數據類型,數字number,字符串String,甚至函數,對象

而且要注意函數式聲明(無狀態)組件與Es6中類聲明組件時,在子組件內部接收props的寫法上的差別,當使用類class聲明一個組件時,定義本身的構造器函數,必定要使用constructor構造器函數,而且設置接收props參數,以及調用super(props),若是不進行該設置,該組件下定義的成員私有方法(函數)將沒法經過this.props訪問到父組件傳遞過來的prop值

固然,在React中,規定了不能直接更改外部世界傳過來的prop值,這個prop屬性只具有讀的能力,具體緣由可見上文

若是非要更改,那麼能夠藉助React提供的setState這一方法進行改變

值得一提的就是關於this壞境綁定的問題,在組件內的constructor構造器函數內使用bind的方式進行this手動綁定設置,具體詳細內容可見上文

以及當知道如何定義組件中的prop數據,還有必要對外部組件傳給內部組件的prop數據類型的校驗,經過prop-types庫來解決,PropTypes這個實例屬性來對prop進行規格的設置,這樣能夠在運行代碼時,能夠根據propTypes判斷外部組件是否整整的使用組件的屬性,輸入輸出的類型是否一一對應,保持一致

限於篇幅所示:React中數據的另外一個state將在下一篇幅中進行學習了

相關文章
相關標籤/搜索