React 學習(一) ---- React Element /組件/JSX


  學習React的時候,你可能聽到最多的就是要先學習webpack, babel,要先學會配置然后才能學react 等等,一堆的配置就把我們嚇着了,根本就沒有心情就學習react了。其實在最開始學習react, 想要了解React 是什么的時候,我們完全不用配置,直接用script標簽 引入React就可以了, 不過要注意,這里需要引入兩個庫:React 和ReactDom。React 用來創建UI, ReactDom 負責把React 創建的UI 渲染到瀏覽器中. 初學react只需下面這個模版就可以了。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello World</title>
    <!-- 引入React 和 ReactDOM -->
    <script src="https://cdn.bootcss.com/react/16.1.0/umd/react.development.js"></script>
    <script src="https://cdn.bootcss.com/react-dom/16.1.0/umd/react-dom.development.js
    "></script>
  </head>
  <body>
    <div id="root"></div>
    <script>
      // react 代碼寫在這里
    </script>
  </body>
</html>

  這里,頁面中還增加了一個id 為root 的div, 它的作用是告訴ReactDom 要把UI 渲染到什么地方。注意,我們這里先不使用JSX語法,從而了解一下JSX的由來,或它底層的東西。好了,我們開始學習React 吧. 

  React Element

  在學習React的時候,遇到的第一個概念可能就是virtual DOM或React element, 它們其實對應的是瀏覽器DOM 或 HTML元素。在做JS 開發的時候, 大家都知道,DOM 操作很慢,我們都會盡量避免操作DOM, 那么怎么才能更快地操作DOM呢?React 提出了virtual DOM的概念, 因為這份DOM 不是在瀏覽器中,而是在內存中,瀏覽器中的DOM是真實的DOM,它能直接渲染出來,但內存中的DOM 又和瀏覽器的DOM 一致,所以叫virtual DOM, 同理,瀏覽器中的DOM是由於一個個HTML element 組成,那么作為對應,virtual DOM 就是由一個個的React element 組成。概念總是抽象的,我們寫一下代碼了解一下。

  React element, 是由React 庫提供的createElement 函數創建而來,我們創建一個React element,

let h1Elem = React.createElement('h1', {id: 'recipe', 'data-type': 'title'}, 'Hello World');

  你可以看到 createElement 函數接受三個參數,第一個是type, 我們要創建哪種類型的React Element, 在這里,我們創建了h1 element. 第二個是properties, 這個element 有哪些屬性,在這里有兩個屬性,一個是id, 一個是data-type. 第三個參數是children, 這個元素有哪些子元素。通過分析,我們也可以猜測出h1 大概長什么樣子。 <h1 id=’recipe’  data-type=’title’ ></h1>

   這時我們打開瀏覽器,刷新一下頁面,什么也沒有,我們創建了react element, 到底發生了什么事情呢?這時我們 console.log(h1Elem)  一下

  我們看到它只是一個Js 對象,擁有props,type等等屬性, 和html element 沒有任何關系, 但是通過它,我們卻准確地描述了 html element 長什么樣子,所以它是virtual, 用於指導構造真正的html element.

  當React element 多了起來,層層嵌套,就成了一個DOM 樹,這就是所謂virtual DOM. createElement 方法怎么才能形成層層嵌套關系呢? 這主要在於這個函數可以接受任意個參數,從第三個參數及其以后的參數,都會當成創建的element 的children 來對待。我們創建一個ul--li 的結構

let ulElem = React.createElement(
        'ul', 
        null ,
        React.createElement('li', null, 'JS'),
        React.createElement('li', null, 'React'),
        React.createElement('li', null, 'Redux')
      );

   通過這個createElement 函數,我們也可以很清楚地知道它代表下面的ul-li

  當我們用React 創建出虛擬Dom, 怎樣才能渲染到html 頁面中,形成真實的DOM呢?那就用到了react-dom 庫了,它里面有一個render 方法, 可以把虛擬DOM 轉化成真實的DOM,  用法如下

 ReactDOM.render(ulElem, document.getElementById('root'));

  它接受兩個參數,一個是要轉化的Element, 一個是渲染到什么地方, 這時我們刷新瀏覽器,可以看到3個li, 和我們用createElement 

創建的元素一一對應。

  React Component

  當我們寫大量的React element  的時候,你會發現有些代碼可以共用,就像我們上面創建的ulElem,可以用到很多地方,這時我們就想把它們封裝起來,這就形成了React component, 組件。在js 中代碼封裝有兩種方法,一種是函數,一種是對象,相應的React 也提供了創建組件的兩種方法,一種是類式,類式就是對象封裝,一種是函數式。

  類式,則是利用es6 class 語法, 我們所有的組件都繼承自React.Component,在render 函數中返回React Element

      class Content extends React.Component {

        render() {
          return React.createElement('ul', null ,
            React.createElement('li', null, 'JS'),
            React.createElement('li', null, 'React'),
            React.createElement('li', null, 'Redux')
          );
        }
      }

  函數式,就是一個函數,返回React element

      const Content = () => {
        return  React.createElement('ul', null, 
          React.createElement('li', null, 'JS'),
          React.createElement('li', null, 'React'),
          React.createElement('li', null, 'Redux')
        )
      }

  我們聲明了組件以后,怎么使用這個組件呢? 這時要注意,我們的組件名稱(Content),只是相當於createElement 函數中的第一個參數type, 它不是一個React element, 它只是返回一個React element, 我們仍然需要調用React.createElement 來創建一個React element

      // 我們創建的組件名Content, 只是相當於createElement函數中的第一個參數type, 它是相當於h1的type, 而不是一個React Elemnet. 利用這個type,仍需要創建組件。
      let content = React.createElement(Content, null, null);
      
      ReactDOM.render(content, document.getElementById('root'));

  這時頁面中同樣顯示三個li, 表示成功。

  總結: 創建組件,只是把一段可以重用的React element 進行封裝,從而創建一個自定義的type, 然后再利用該type, 隨處都可以創建element 元素,進而達到重用的目的。

  React 組件數據

  這時,你會發現我們組件所有的數據都是硬綁定的,能不能把數據和 UI的創建分開,從而使我們的組件更具有通用性,不同的數據渲染出不同的內容? 這是可以的,當我們利用組件創建react element 的時候,第二個參數是null, 可以再回顧一下代碼

let content = React.createElement(Content, null, null);

  我們可以利用這個參數向組件內傳遞數據,因為這個參數就是表示這個組件的屬性,它的形式也是一個鍵值對的形式,比如我們把JS, React ,Redux 數據提出來,形成一個數組,

const item = ['JS', 'React', 'Redux'];
// 向組件中傳遞一個數組數據 item 
let content = React.createElement(Content, {item: item}, null);

  那么我們組件中怎么獲取到這個數據呢?這要分兩種情況,類式的組件則是通過this.props獲取的,而函數式組件,則是通過傳參的形式獲取

  先看類式的組件, 在組件中通過this.props 來獲取, 我們可以把this.props 找印出來看一下

      class Content extends React.Component {
        render() {
          // 打印props;
          console.log(this.props);  // {item: Array(3), children: null}
          return React.createElement('ul', null ,
            React.createElement('li', null, 'JS'),
            React.createElement('li', null, 'React'),
            React.createElement('li', null, 'Redux')
          );
        }
      }

  可以看到this.props 獲取到了我們傳遞的item 數組,那我們就可以直接使用了數組數據了,這時通過數組的map 渲染li

class Content extends React.Component {
  render() {
    return React.createElement('ul', null ,
      // this.props.item 獲取到傳遞過來的數據
      this.props.item.map((item, index) => 
        React.createElement('li', {key:index}, item)
      )
    );
  }
}

  函數式的組件,則是它自動會獲取props作為參數,組件中直接使用props.items 獲取到數據

// 自動獲取props作為參數。
const Content = (props) => {
  return  React.createElement('ul', null, 
    props.item.map((item, index) => 
      React.createElement('li', {key:index}, item)
    )
  )
}

  JSX

  在上面的代碼中,我們每創建一個React Element 都要調用一次React.createElement 函數,非常繁瑣,並且它想表達式的意思只是一個類html的元素,再來看一下我們創建的h1Elem element 

let h1Elem = React.createElement('h1', {id: 'recipe', 'data-type': 'title'}, 'Hello World');

  它實際上表達的意思就是 <h1 id=’recipe’  data-type=’title’ ></h1>, 如果我能在代碼中直接寫h1 就好了。這就是可以了,它就是JSX 語法, 可以直接在js 代碼中寫類html 的語法。React 把createElement 函數作了進一步的封裝,提供了JSX語法。為什么能直接寫呢?其實createElement 函數,和html 元素有一一對應的關系。

  在createElement 函數中,它的第一個參數是type,表示創建什么類型,而在html中,表示什么類型直接用html 標簽,<h1></h1>  它就表示h1 類型, 第二個參數表示屬性,元素有哪些屬性,而在html標簽中,有什么屬性,就直接寫在它的標簽中,有多少,寫多少, 如 <h1 id='recipe' class='title'></h1>. 第三個參數是children, 在html中表示children更簡單,直接寫在兩個標簽內部的內容都是children. 這樣一一對應以后,就可以理解JSX 寫法的用意了,在心理上寫起來就比較舒服了,因為明白了。

  對於組件來說,它也是一樣的,因為組件名稱,只是一個type, 仍然需要調用createElement 函數來 創建React Element 元素, 只要使用createElement 的地方,我們都可以使用類html 語法,如Content組件,<Content></Content>  就表示創建了一個element了。它的屬性和children和上面的h1 用法一致。對於Content 組件,如果沒有 chilren 屬性,可以直接寫單標簽<Content />.   現在用JSX的語法來書寫Content 組件。

class Content extends React.Component {
  render() {
    return (
      <ul>
        {
          this.props.item.map((item, index) => 
            <li key={index}>{item}</li>
          )
        }
      </ul>  
    )
  }
}

// 向組件中傳遞一個數組數據 item 
let content = <Content item ={item}></Content>

  無論是在組件屬性,還是元素屬性中,我們都使用了{}, 如key={index}. 在jsx 中,{}里面的所有東西都當作js表達式進行解析, React 會把里面的內容進行求值計算。只要寫表達式,我們都要用{} 括起來。 在JSX 中還有另外一種屬性,就是字符串,它可以直接寫如name="sam",除此之外,所有的屬性都要用{} 括起來。比如我們向組件中傳遞一個數字1,我們就要寫 num = {1}, 傳遞一個布爾值,就要寫 bool={false},  我們傳遞一個字符串,一個布爾值, 一個數字體驗一下。

// 組件添加了三個p, 有來接受數據
class Content extends React.Component {
  render() {
    return (
      <section>
        <ul>
          {
            this.props.item.map((item, index) => 
              <li key={index}>{item}</li>
            )
          }
        </ul>  
        <p>name 的值是{this.props.name},類型是 {typeof this.props.name}</p>
        <p>bool 的值是{this.props.bool},類型是 {typeof this.props.bool}</p>
        <p>num 的值是{this.props.num},類型是 {typeof this.props.num}</p>
      </section>
    )
  }
}
// 向組件中另外傳遞 字符串name ,布爾值bool, 一個數字num。
let content = <Content item ={item} name="sam" bool={false} num={1}></Content>

  這時刷新瀏覽器,可以看到報錯了,首先,JSX語法,瀏覽器是不支持的,我們要把它轉換成JS, 所以引入babel庫,在head 標簽中引入

    <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>

  其次我們要告訴babel, 我們的代碼需要轉譯,所以在寫react 代碼的script 標簽上添加一個type 屬性

<script type="text/babel">

  這時再刷新瀏覽器,沒有問題了,我們也獲得了屬性,並且它的類型也是對的,num 是Number, 字符串是String。

  但是這里有一個問題,就是如果我們要傳遞很多屬性,這么 一個一個列出來,非常麻煩,怎么辦呢? 這時可以使用對象,但如果用對象進行傳值,又不符合 屬性名=屬性值的寫法,這時要用到es6中的擴展運算符..., React 對es6 中的擴展運算符(…)進行擴展,它能運用到對象上,對對象進行分割。{…obj}; var obj = {name:”sam”, num: 1}  , …obj  => name=”sam” , num= 1, 注意,...obj 是一個表達式,仍需要把它用{} 括起來

<script type="text/babel">

  // 組件添加了三個p, 有來接受數據
  class Content extends React.Component {
    render() {
      return (
        <section>
          <ul>
            {
              this.props.item.map((item, index) => 
                <li key={index}>{item}</li>
              )
            }
          </ul>  
          <p>name 的值是{this.props.name},類型是 {typeof this.props.name}</p>
          <p>bool 的值是{this.props.bool},類型是 {typeof this.props.bool}</p>
          <p>num 的值是{this.props.num},類型是 {typeof this.props.num}</p>
        </section>
      )
    }
  }

  // 要把傳遞的屬性寫到一個對象中,
  const obj = {
    item : ['JS', 'React', 'Redux'],
    name: 'sam', 
    bool: false,
    num: 1
  }

  // 把對象進行分割
  let content = <Content {...obj}></Content>
  ReactDOM.render(content, document.getElementById('root'));
</script>

  在Content 組件中,你可能發現外層包了一個section, 這是因為所有的組件都返回一個單一的根節點,主要還是createElemet 函數第一個屬性type 是一個值, 不能接受多個值。

你可能還發現所有的組件名是大寫,還是因為createElement 的第一個參數type, type 有兩種類型,一種是html原有的類型如h1, 一種是自定義的類型,就是component, 當我們傳入時,React  無法區分這兩種類型,所以它用大小寫進行區分。如果是小寫,它就以為是html原有的類型,如果是大寫,就是自定義類型。如果我們組件使用了小寫,React 按html原有的類型進行渲染,但是它又找不到這個類型,所以什么都不會渲染,組件名必須大寫。

   在JSX語法中, 我們還要注意以下兩點:

  html 屬性關鍵字: 如 我們可以給html元素添加屬性<h1 class="book">, 但class 在js 中是關鍵字, 所以class 要變成 className. 由於JSX 最終會轉換成原生js 函數,所以js中的一些關鍵字在JSX中是不能用的,如class, for. 但在JSX 的類html 模版中,html 元素屬性中又有class 和for, 這就沖突了。React 對html 元素中有沖突的屬性進行了重新命名,for 變成了htmlFor, class 變成了className.  所有變量的命名都要用 駝峰命名法。如label 元素 <label htmlFor=”input” className=”text”></label>

  樣式:在JSX 中,給一個html 元素添加樣式有兩種方法,一種是上面提到的className,  它的取值是一個樣式名字符串,一種是內聯樣式style, 它的取值必須是一個對象。 <div className=”col-md-3” style ={style}></div> ,style 是組件內部定義的一個對象變量。 因為render 是一個函數,里面可以聲明變量

  class Content extends React.Component {
    render() {
    // 定義樣式變量
  var inlineStyle = {
      color: 'green' ,
   // css3 一些屬性有些需要帶瀏覽器廠商前綴,這時廠商前綴首字母必須大寫, 所有的樣式都是字符串
   WebkitFilter: blur('5px')
  } 

    return (
      <section>
        <p style={inlineStyle}>name 的值是{this.props.name},類型是 {typeof this.props.name}</p>
      </section>
    )
    }
  }

  


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com