大家一起來
React是一個用於構建使用者介面的JS庫,核心專注於檢視,目的實現元件化開發
所謂元件化開發,其實就像堆積木一樣,每個元件都包含了自己的邏輯和樣式,然後再組合到一起完成一個複雜的頁面
元件化的主要特點就是:可組合、可複用、可維護
那麼廢話不多說,讓我們直接進入今天的主題,運用官方推薦的腳手架來搭建一個react專案開始學習吧
create-react-app啟動react專案
第一步:全域性安裝create-react-app腳手架
npm i create-react-app -g
複製程式碼
第二步:建立react專案
create-react-app 專案名
// 如:create-react-app react123
複製程式碼
通過以上操作就會自動建立一個名為react123的react專案了,在建立的過程中,腳手架會自動為你安好react的核心包,react和react-dom,在此過程完成後即可進入第三步了
第三步:進入專案並啟動服務
cd react123 && npm start
複製程式碼
通過上面的三步曲,就會自動彈出瀏覽器訪問一個localhost:3000(預設為3000埠)的react頁面了
現在,讓我們通過進入建立好的react專案中,先去看一下搭建好的結構是什麼樣的?
下面就為大家展示一番:
根據上圖畫圈所示,public目錄下的index.html表示的是專案的主靜態檔案(包含依賴的節點),開啟後發現一大堆內容,其實都可以刪掉,只需要留一個root即可<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<title>React App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
複製程式碼
說完public後,再說一下src目錄,這裡面的檔案也都可以刪掉(除了index.js主入口檔案),這裡就和webpack4裡預設的入口檔案比較類似了
先來了解下jsx
在src下的index.js中,我們就可以開啟React之旅了,React主要用的是Facebook自己開發的jsx語法,這是一種js和xml的集合體,直接上程式碼
// 首先引入react的核心包
// 這裡的命名必須叫React,如果不是的話會報錯
import React from 'react';
// 這裡主要用到render方法,所以直接將其解構出來使用
import { render } from 'react-dom';
// jsx語法通過babel來轉義,通過預設的babel-preset-react來進行轉義
// 將jsx語法解析成js來使用
let ele = (
<h1 className="big">
hi, <span>baby</span>
</h1>
);
複製程式碼
我們可以將寫好的jsx語法直接放到babel官網來解析一下
// 解析後的程式碼如下
React.createElement(
"h1",
{ className: "big" },
"hi, ",
React.createElement(
"span",
null,
"baby"
)
);
複製程式碼
通過React.createElement來建立一個虛擬DOM,其中包含三個引數,分別是type、props和children
- type: 對應的標籤h1
- props: 對應的屬性{ className: "big" }
- children: 對應的子節點 -> 字串'hi, ' 和一個新的React.createElement( "span", null, "baby" )
其實按照React.createElement建立的物件來看,列印出來後的結構是這樣滴
根據上面列印的結構來看,其實它們都是react.element上的例項,那麼就來針對列印後的物件進行一個主要的抽離,來看看是何方神聖吧{
type: 'h1',
props: {
children: [
'hi',
{
type: 'span',
props: {
children: 'baby'
}
}
],
className: 'big'
}
}
複製程式碼
在我們轉化完物件後,就可以通過ReactDOM提供的render方法去渲染這樣的一個物件,最後將其渲染到頁面中
上面寫好的ele程式碼塊,其實就是React.createElement的語法糖,畢竟每次寫那麼一大坨React.createElement的話,十有八九會寫出錯的(我覺得可能是100%會出錯,哈哈)
所以我們看到這裡可以簡單的梳理一下整個渲染的執行順序了
jsx語法 -> createElement格式 -> 轉化成物件 -> 物件轉換成真實dom - > render方法渲染
import React from 'react';
import { render } from 'react-dom';
// React.createElement的語法糖
let ele = (
<h1 className="big">
hi, <span>baby</span>
</h1>
);
render(ele, window.root); // 將ele渲染到root上
複製程式碼
下圖是將虛擬dom渲染到root節點下的情況
上面寫的程式碼不多,主要就是簡單寫了個jsx的語法,然後通過render進行了渲染而已。在寫ele的時候,我們發現和正常的html還是很相像的,但是我在這裡要說一下, 其實它們不一樣,接下來就看看有哪些地方不一樣吧!
jsx和html寫法的區別
- className 它會轉化成 class
- htmlFor 它要轉化成for屬性 label for
- jsx元素可以巢狀
- 相鄰的react元素,必須要加一層包裹起來
- jsx裡面可以寫js,{}裡面可以寫js語句
- 只支援多行註釋,{/* ... */},不過很少寫註釋
- style標籤必須寫成一個物件,如:{ background: 'skyblue' }
說了這幾點區別,直接開擼,沒有程式碼更讓人直觀的喜愛了,hurry up go go go
import React from 'react';
import { render } from 'react-dom';
// 提示:下面註釋標記的數字對應上面區別的序號,序號5和6就不寫了
let singer = '周杰倫';
let style = { backgroundColor: '#0cc', color: '#fff', fontSize: '14px' };
let album = '跨時代';
let arr = ['晴天', '陰天', '下雨天'];
let ele = (
/* 4.相鄰的react元素外層必須包起來(h1,label,input,p它們都是相鄰的),
vue是用一個template標籤包起來,
可以寫一個div包起來,不過會產生一個多餘的div標籤
react直接採用React.Fragment來包裹最佳
*/
<React.Fragment>
{ /*1.渲染後會轉成<h1 class="song">煙花易冷</h1>*/ }
<h1 className="song">
煙花易冷-
{/* 3.jsx元素可以巢狀 */}
<span>{singer}</span>
</h1>
{/* 2.htmlFor會轉成<label for="inp"></label>
點選label標籤會自動獲取到input焦點 */}
<label htmlFor="inp">獲得焦點</label>
<input type="text" id="inp" />
{/* 7.style必須是物件的形式,如style={{color: '#fff'}} */}
<p style={style}>{album}</p>
{arr.map((item, index) =>{
return <li key={index}>{item}</li>
})}
</React.Fragment>
);
render(ele, window.root);
複製程式碼
按照上面的程式碼可以直觀的看到一些具體的區別,那麼心急吃不了臭豆腐,趕緊開始繼續往下寫吧。就這點東西才不是react的兩把刷子呢,下面有請react的元件閃亮登場!
元件
在react中元件分為兩種,一種是函式元件,一種是類元件,那麼如何區分是不是元件呢?這裡有一個標準,首字母要大寫
what?是的,沒錯,小寫的就是jsx元素了,哈哈,不信你來看
函式元件
import React from 'react';
import { render } from 'react-dom';
// 此為函式元件
function Song(props) {
return (
<div className="wrap">
<h1>晴天-{props.singer}</h1>
<p>為你翹課的那一天,花落的那一天</p>
<p>教室的哪一間,我怎麼看不見</p>
</div>
);
}
// 元件可以通過屬性傳遞資料
// render方法不會一直渲染,只會渲染一次
render(<Song singer="周杰倫" />, window.root)
複製程式碼
當然有些人可能不信,如果我把Song寫成song,然後再渲染,能有什麼問題呢?
其實沒有問題,那是不可能的,因為人家react瞭解你,會給你一個報錯告訴你是不是想使用一個react元件進行渲染,那就把首字母大寫
官方說的,童叟無欺了,哈哈
繼續回到函式元件的探討中,函式元件是個無this,無生命週期,無狀態的三無產品。所以可想而知使用頻率也是不如類元件的。
舉個例子:
import React from 'react';
import { render } from 'react-dom';
// 函式元件Clock
function Clock(props) {
return <p>{props.date}</p>
}
setInterval(() => {
render(<Clock date={new Date().toLocaleString()} />, window.root);
}, 1000);
複製程式碼
可以實時的更新時間,不過由於函式元件沒有狀態,而且只會渲染一次,所以還要在外層加一個setInterval定時器,就有點不倫不類了
更重要的是,狀態的更改都是通過屬性由我們傳遞過去的,沒有自己的狀態,重新整理的時候都依賴傳遞,這樣很不好,元件的特點就是複用
所以我們就要隆重的請出元件中的巨無霸,類元件閃亮登場
類元件
還是上面的栗子,我們改成類元件的形式
// 由於類元件需要繼承react的Component,所以直接解構出來使用
import React, { Component } from 'react';
import { render } from 'react-dom';
class Clock extends Component { // 繼承Component
constructor() {
super(); // 繼承後可以使用setState方法
// 設定元件的預設狀態
this.state = { date: new Date().toLocaleString(), pos: '北京' }
}
// 需要提供一個render方法返回dom物件
render() {
return (
<React.Fragment>
<p>當前時間:{this.state.date}</p>
<p>座標:
<span style={{color: 'skyblue'}}>{this.state.pos}</span>
</p>
</React.Fragment>
);
}
// 一不小心就展示了目前的第一個生命週期了
// componentDidMount生命週期
// 當元件渲染完成後呼叫此生命週期
componentDidMount() {
setInterval(() => {
// 重新設定date的狀態值,
// this.setState可以更改狀態重新整理頁面
this.setState({date: new Date().toLocaleString()});
}, 1000);
}
}
render(<Clock />, window.root);
複製程式碼
上面通過類元件完成了同樣的效果,接下來我們好像還少些什麼?事件?沒錯,我們寫js怎麼能不新增事件呢,下面就來說一下如何新增事件
接著上面的程式碼,我們繼續寫,再敲一遍,不怕累,熟能生巧,是勝利
import React, { Component } from 'react';
import ReactDOM, { render } from 'react-dom';
class Clock extends Component {
constructor() {
super();
this.state = { date: new Date().toLocaleString(), pos: '北京' };
// 強制繫結this
this.handleClick = this.handleClick.bind(this);
}
/* 繫結方法有幾種方式 方法中可能會用到this
1.箭頭函式 (不提倡,會產生個新函式,樓下2層也是)
2.bind繫結 this.handleClick.bind(this)
3.在建構函式中繫結this (官方推薦)
4.ES7語法可以解決this指向 handleClick = () => {} (更推薦,哈哈)
*/
handleClick = () => {
console.log(this) // 直接綁給一個函式的話,此時的this是undefined
// 在方法裡,我們來移除一下這個元件
// 移除元件我們就用到了ReactDOM的方法
ReactDOM.unmountComponentAtNode(window.root);
}
render() {
// 這裡繫結個click事件,用的是駝峰寫法onClick
// 後面要跟js語法,onClick={}
return (<React.Fragment>
<p onClick={this.handleClick}>當前時間:{this.state.date}</p>
<p>座標:
<span style={{color: 'skyblue'}}>{this.state.pos}</span>
</p>
</React.Fragment>)
}
componentDidMount() {
this.timer = setInterval(() => {
this.setState({ date: new Date().toLocaleString() });
}, 1000);
}
componentWillUnmount() {
clearInterval(this.timer);
}
}
render(<Clock />, window.root);
複製程式碼
執行以上程式碼後,點選當前時間的p標籤後,就會在root節點下移除掉該元件(dom)了,不過可能大家會忽略掉一些常見的問題,下面我就來解釋一下
- 當點選移除元件後,定時器仍在執行,setState還在更新著date的狀態,所以會有報錯的
- 下面就著重解釋一下這個問題
class Clock extends Component {
// 主要看這裡(氣質)
componentDidMount() {
// 和以往清除定時器的做法一樣,我們直接將timer掛到this例項上
this.timer = setInterval(() => {
this.setState({date: new Date().toLocaleString()})
});
}
// 這個生命週期是元件將要被解除安裝的時候呼叫
componentWillUnmount() {
// 一般解除安裝元件後要移除定時器和繫結的方法
clearInterval(this.timer);
}
}
複製程式碼
好了,上面的報錯問題就這樣迎刃而解了。不過,說的總比唱的好聽,為什麼要將timer放到this上?放到this.state裡的timer狀態不行嗎?
放到this.state上的話說明,當前頁面要依賴這個狀態,狀態變了檢視可以重新整理,setState是可以更新頁面的
然而將timer直接放到當前例項上(this),即時刪掉(清空)了,也不影響頁面
元件講了這麼多,讓我們做個小結吧
- 元件有兩個資料來源
- 一個是屬性 外界傳遞的
- 一個是狀態 自己擁有的
小結是完畢了,那就喝杯水繼續往下看吧,我們發現react的狀態很有分量啊,state狀態改變後可以重新整理檢視改變,屌不屌!吊炸天啊簡直,那麼我們就單獨針對這個state來說上一說吧
state狀態
蒼白的文字,不如真實的程式碼看的帶勁,上程式碼
// 這是一個很常見很常見的栗子,計數器
import React, { Component } from 'react';
import { render } from 'react-dom';
class Counter extends Component {
constructor() {
super();
this.state = { count: 1 };
}
handleClick = () => {
// 寫到這裡就可以實現點選一次button就加1一次了
// this.setState({ count: this.state.count + 1 });
// this.setState也可以接受一個函式,它的第一個引數就是上一次的狀態
//
this.setState(prevState => ({ count: prevState.count + 1 }));
this.setState(prevState => ({ count: prevState.count + 1 }));
this.setState(prevState => ({ count: prevState.count + 1 }));
}
render() {
return (
<div>
<span>計數器: </span>
{this.state.count}
<button onClick={this.handleClick}>點我</button>
</div>
);
}
}
render(<Counter></Counter>, window.root);
複製程式碼
state有個特點:多個狀態可以批量更新?就是說如果我上面寫了三個this.setState去改變count,那按常理來說,狀態就應該改變三次,點一次button就加3了
其實不然,react不會這麼做,如果狀態改變一次,就重新整理一次頁面的話,那就瘋狂了,react是把狀態(count)先記住,最後再一起重新整理頁面
於是乎就用上面重新設定的setState來保留count的狀態,最後再一次性的改變。說白了就是如果更新時下一次的狀態依賴於上一次的狀態,就得寫成函式的形式
好了,下面再來說說元件間的通訊吧,先來個父子元件直接的通訊
元件間的通訊
元件間的通訊,父子元件之間就是通過屬性傳遞的,父 -> 子 -> 孫子
單向資料流,資料方向是單向的,子不能改父的屬性 下面來看個小demo
父元件 songs.js
import React, { Component } from 'react';
import { render } from 'react-dom';
import axios from 'axios'; // axios用於請求
import List from './list'; // 子元件
import Audio from './audio'; // 子元件
import './css/songs.css';
class Songs extends Component {
constructor() {
super();
this.state = { songs: [], mp3Url: '', isPlay: false };
}
// 子傳父通訊 -> 父提供一個方法,子呼叫後將引數回傳
// chooseSong為選擇歌曲地址填入audio的src的方法
chooseSong = (url, isPlay) => {
// 這裡子元件回傳了屬性更改了isPlay的狀態
// 對應修改了List的同級元件Audio接收的played屬性值
this.setState({mp3Url: url, isPlay});
}
render() {
return (
<div className="songs-box">
<ul>
{this.state.songs.map((item, index) => (
/*
通過傳遞屬性的方式進行通訊
這裡將item解構出來,然後List子元件按照需要去拿資料
*/
<List key={index} {...item} choose={this.chooseSong}></List>
))}
</ul>
<Audio url={this.state.mp3Url} played={this.state.isPlay}></Audio>
</div>
);
}
async componentDidMount() {
// 在react中傳送ajax請求 現在我們用axios
// axios封裝了RESTFul 基於promise的 不支援jsonp 可用在服務端
let { data } = await axios.get('http://musicapi.leanapp.cn/search?keywords=林俊杰');
this.setState({ songs: data.result.songs });
}
}
render(<Songs></Songs>, window.root);
複製程式碼
未播放效果
播放效果 剩餘的程式碼我就不一一展示了,僅僅是個簡單的小demo,功能很多都未完善,有興趣的同學可以去研究一下繼續寫寫吧,貼個地址吧通過上面的小demo,其實可以發現元件間的通訊主要有三種方式
- 第一種方式是通過屬性傳遞,父 -> 子 -> 孫子 (父傳子)
- 單向資料流,資料方向是單向的,子不能改父的屬性
- 第二種方式是父寫好了一個方法,傳遞給兒子 (子傳父)
- 兒子呼叫這個方法,在這個方法中可以去更改狀態
- 第三種方式是同級元件傳遞
- 同級元件想要傳遞資料,可以找到共同的父級,沒有父級就創造父級
受控元件和非受控元件
接下來了解一下受控元件與非受控元件的兩個概念,他們指的是表單元素
既然是涉及到了表單元素,那我們期待的雙向資料繫結應該也是該登場的了
雙向資料繫結
import React, { Component } from 'react';
import { render } from 'react-dom';
class Input extends Component{
constructor(){
super();
this.state = {val: '你好'};
}
// 通過onChange事件呼叫該方法後,每次更改輸入後val的狀態值
handleChange = (e) =>{ //e是事件源
let val = e.target.value;
this.setState({val});
};
render(){
return (<div>
<input type="text" value={this.state.val} onChange={this.handleChange}/>
{this.state.val}
</div>)
}
}
render(<Input />, window.root);
複製程式碼
雙向資料繫結主要通過的是監聽onChange事件,然後每次都將新輸入的value值更改對應val的狀態,從而再賦給input的value屬性,做到了資料雙向傳遞的實現,果然很高階,哈哈
受控元件
受狀態控制的元件,必須要有onChange方法,否則不能使用
受控元件可以賦予預設值
import React, { Component } from 'react';
import { render } from 'react-dom';
class App extends Component{
constructor(){
super();
this.state = {a: '破風', b: '激戰'};
}
// name表示的就是當前狀態改的是哪一個
// e表示的是事件源
handleChange = (e) => { //處理多個輸入框的值對映到狀態的方法
let name = e.target.name;
this.setState({ [name]: e.target.value });
}
render(){
return (
<form>
<input type="text"
required={true}
value={this.state.a}
onChange={this.handleChange}
name="a"
/>
<input type="text"
required={true}
value={this.state.b}
onChange={this.handleChange}
name="b"
/>
<input type="submit" />
<p>{this.state.a}</p>
<p>{this.state.b}</p>
</form>
)
}
}
render(<App></App>, window.root);
複製程式碼
最後再來看一下非受控元件
非受控元件
所謂非受控元件有三個特點:
- 可以操作dom,獲取真實dom
- 可以和第三方庫結合
- 不需要對當前輸入的內容進行校驗,也不需要預設值
import React, { Component } from 'react';
import { render } from 'react-dom';
// 1.函式的方式 ref
// 2.React.createRef() v16.3+
class App extends Component {
constructor() {
super();
this.aaa = React.createRef();
}
componentDidMount() {
// this.aaa.focus(); // 對應1
this.aaa.current.focus(); // 對應2
}
render() {
return (<div>
{/* 1.<input type="text" ref={input=>this.aaa = input} />*/}
{/* 2.會自動的將當前輸入框 放在this.aaa.current */}
<input type="text" ref={this.aaa} />
</div>)
}
}
render(<App />, window.root);
複製程式碼
上面的栗子呢,就是一進入頁面後就會自動獲取輸入框的焦點了,之後大家可以試著敲敲看吧
時間不早,節目剛好
雖然react還有很多的內容要講,而且還沒有涉及到生命週期等內容
不過,大家放心,該來的總會來,一個都不能少,在之後的文章裡,我會繼續學習和分享的
那麼今天就到這裡了,謝謝大家不辭辛勞的觀看,各位,安啦!!!