初學react,想要了解React 是什么,直接用script標簽 引入React就可以了,不過需要引入兩個庫:React 和ReactDom,React 負責創建React element,ReactDom 則是負責把React創建出來的element, 通過調用DOM API, 創建出真實的DOM 元素,這樣瀏覽器就可能根據DOM渲染出頁面了。模板如下
<!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>
React Element
一個普通的JavaScript 對象,用來描述真實的DOM 長什么樣子,可以通過React 庫提供的createElement() 函數來創建。createElement 函數接受三個參數,第一個是type, 要創建什么類型的element, 第二個是properties, 這個element 有哪些屬性,第三個參數是children, 這個element有哪些子元素。
let h1Elem = React.createElement('h1', {id: 'recipe', 'data-type': 'title'}, 'Hello World');
這時打開瀏覽器, console.log(h1Elem) 一下, 看一看這個剛剛創建的React element 是不是一個普通的對象。
它是一個Js 對象,擁有props,type等等屬性, 但是通過它,也確實描述了 一個DOM元素 長什么樣子,<h1 id='recipe', data-type='title'>Hello Word</h1>,所以它也稱之為virtual DOM, 用於指導構造真正的DOM 元素.
當然,createElement 不僅能創建一個element, 還可以創建一個層層嵌套的element樹, 這主要在於函數可以接受任意個參數,第三個參數及其以后的參數,都會當成element 的children
let ulElem = React.createElement( 'ul', null , React.createElement('li', null, 'JS'), React.createElement('li', null, 'React'), React.createElement('li', null, 'Redux') );
此時也可以console.log(ulElement), 也可以看到它是一個嵌套的js 對象。
{ type: 'ul', props: { children: [ { type: 'li', props: {children: 'JS'} }, { type: 'li', props: {children: 'React'} }, { type: 'li', props: {children: 'Redux'} } ] } }
通過ulElem,我們也可以很清楚地知道頁面上將要展示什么,就是下面的ul-li,
創建完React element, 就需要用到ReactDom 了。因為React element只是js 對象,只是描述了真實的DOM長什么樣子,最后還是要創建真實的DOM,否則瀏覽器無法渲染。ReactDom提供了一個render 方法, 可以把React element(虛擬DOM)轉化成真實的DOM
ReactDOM.render(h1Elem, document.getElementById('root'));
第一個參數是React element, 第二個參數是渲染到什么地方, ReactDom render 相當於執行下面的操作
let h1 = document.createElement('h1'); h1.setAttribute('id', 'recipe'); h1.setAttribute('data-type', 'title'); h1.textContent = 'Hello World'; document.getElementById('root').appendChild(h1);
這時我們刷新瀏覽器,可以h1. 如果React element 像ul 這樣包含children的話,ReactDom render 將執行遞歸操作,依次創建DOM。
React Component
當我們寫大量的React element,你會發現有些代碼可以共用,就像上面創建的ulElem,可以用到很多地方,這時我們就想把它們封裝起來,這就形成了React component組件。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 組件數據
以上創建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’ >Hello World</h1>, 如果我能在代碼中直接寫h1 就好了。這就是JSX 語法, 可以直接在js 代碼中寫類html 的語法。React 把createElement 函數作了進一步的封裝,提供了JSX語法。
在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 會把里面的內容進行求值計算。只要寫表達式,我們都要用{} 括起來。向組件中傳遞一個數字1,我們就要寫 num = {1}, 傳遞一個布爾值,就要寫 bool={false}。字符串除外,它可以直接寫。比如可以直接寫如name="sam"。
// 組件添加了三個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> ) } }