目錄
一、React簡介
React 起源於 Facebook 的內部項目,因爲該公司對市場上所有 JavaScript MVC 框架,都不滿意,就決定自己寫一套,用來架設 Instagram 的網站(https://www.instagram.com/)。做出來以後,發現這套東西很好用,就在2013年5月開源了(https://github.com/facebook/react)。由於 React 的設計思想極其獨特,屬於革命性創新,性能出衆,代碼邏輯卻非常簡單。所以,越來越多的人開始關注和使用,認爲它可能是將來 Web 開發的主流工具。
官方網站:https://react.docschina.org/
英文官方網站:https://reactjs.org/
推薦書籍:https://blog.csdn.net/xutongbao/article/details/88638078
菜鳥教程:https://www.runoob.com/react/react-tutorial.html
NPM下載量對比:https://npmcharts.com/compare/react,vue?interval=30
instagram:
react github:
二、React的背景和原理
在Web開發中,我們總需要將變化的數據實時反應到UI上,這時就需要對DOM進行操作。而複雜或頻繁的DOM操作通常是性能瓶頸產生的原因(如何進行高性能的複雜DOM操作通常是衡量一個前端開發人員技能的重要指標)。
React爲此引入了虛擬DOM(Virtual DOM)的機制:在瀏覽器端用Javascript實現了一套DOM API。
Virtual DOM 本質上就是在 JS 和 DOM 之間做了一個緩存。可以類比 CPU 和硬盤,既然硬盤這麼慢,我們就在它們之間加個緩存:既然 DOM 這麼慢,我們就在它們 JS 和 DOM 之間加個緩存。CPU(JS)只操作內存(Virtual DOM),最後的時候再把變更寫入硬盤(DOM)。
React在Virtual DOM上實現了DOM diff算法,當數據更新時,會通過diff算法計算出相應的更新策略,儘量只對變化的部分進行實際的瀏覽器的DOM更新,而不是直接重新渲染整個DOM樹,從而達到提高性能的目的。
你給我一個數據,我根據這個數據生成一個全新的Virtual DOM,然後跟我上一次生成的Virtual DOM去 diff,得到一個Patch,然後把這個Patch打到瀏覽器的DOM上去。
你不要一步一步告訴我這件事情怎麼做,什麼先和麪再剁餡,NO,告訴我你想要煎餅還是月餅,我會想辦法去做的,不要來干擾我。
哪怕是我生成了virtual dom,哪怕是我跑了diff,但是我根據patch簡化了那些DOM操作省下來的時間依然很可觀。所以總體上來說,還是比較快。
用 JavaScript 對象結構表示 DOM 樹的結構;然後用這個樹構建一個真正的 DOM 樹,插到文檔當中當狀態變更的時候,重新構造一棵新的對象樹。然後用新的樹和舊的樹進行比較,記錄兩棵樹差異,把差異應用到真正的DOM樹上,視圖就更新了。
三、JSX簡介
HTML 語言直接寫在 JavaScript 語言之中,這就是 JSX(JavaScript and XML) 的語法。JSX,是一種 JavaScript 的語法擴展,它允許 HTML 與 JavaScript 的混寫。JSX是facebook爲React框架開發的一套語法糖,語法糖又叫做糖衣語法,是指計算機語言中添加的某種語法,這種語法對語言的功能並沒有影響,但是更方便程序員使用,它主要的目的是增加程序的可讀性,從而減少程序代碼錯處的機會。JSX就是JS的一種語法糖,類似的還有CoffeeScript、TypeScript,最終它們都會被解析成JS才能被瀏覽器理解和執行,如果不解析瀏覽器是沒有辦法識別它們的,這也是所有語法糖略有不足的地方。
const element = <h1>Hello, world!</h1>;
上面這種看起來可能有些奇怪的標籤語法既不是字符串也不是HTML,被稱爲 JSX,JSX帶來的一大便利就是我們可以直接在JS裏面寫類DOM的結構,比我們用原生的JS去拼接字符串,然後再用正則替換等方式來渲染模板方便和簡單太多了。推薦在 React 中使用 JSX 來描述用戶界面。JSX 用來聲明 React 當中的元素, 乍看起來可能比較像是模版語言,但事實上它完全是在 JavaScript 內部實現的。
你可以任意地在 JSX 當中使用 JavaScript 表達式,在 JSX 當中的表達式要包含在大括號裏。例子如下:
const names = ['Jack', 'Tom', 'Alice'];
const element = (
<div>
{ names.map(function (name) { return <div>Hello, {name}!</div>}) }
</div>
);
在書寫 JSX 的時候一般都會帶上換行和縮進,這樣可以增強代碼的可讀性。與此同時,推薦在 JSX 代碼的外面擴上一個小括號,這樣可以防止分號自動插入的bug。
上面我們聲明瞭一個names數組,然後遍歷names數組在前面加上Hello,生成了element數組。JSX 允許直接在模板插入 JavaScript 變量。如果這個變量是一個數組,則會展開這個數組的所有成員。JSX 本身其實也是一種表達式,在編譯之後,JSX 其實會被轉化爲普通的 JavaScript 對象。代碼如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello World</title>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/[email protected]/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const names = ['Jack', 'Tom', 'Alice'];
const element = names.map(function (name, index) { return <div key={index}>Hello, {name}!</div>});
ReactDOM.render(
element,
document.getElementById('root')
)
</script>
</body>
</html>
JSX屬性:
你可以使用引號來定義以字符串爲值的屬性:
const element = <div tabIndex="0"></div>;
也可以使用大括號來定義以 JavaScript 表達式爲值的屬性:
const element = <img src={user.avatarUrl}></img>;
切記當使用了大括號包裹的 JavaScript 表達式時就不要再到外面套引號了。JSX 會將引號當中的內容識別爲字符串而不是表達式
四、React組件
組件作爲React的核心內容,是View的重要組成部分,每一個View頁面都由一個或多個組件構成,可以說組件是React應用程序的基石。在React的組件構成中,按照狀態來分可以分爲有狀態組件和無狀態組件。
所謂無狀態組件,就是沒有狀態控制的組件,只做純靜態展示的作用,無狀態組件是最基本的組件形式,它由屬性props和渲染函數render構成。由於不涉及到狀態的更新,所以這種組件的複用性也最強。
有狀態組件是在無狀態組件的基礎上增加了組件內部狀態管理,有狀態組件通常會帶有生命週期lifecycle,用以在不同的時刻觸發狀態的更新,有狀態組件被大量用在業務邏輯開發中。
接下來我們封裝一個輸出 "Hello World!" 的組件,組件名爲 HelloMessage:
function HelloMessage(props) {
return <h1>Hello World!</h1>;
}
const element = <HelloMessage />;
ReactDOM.render(
element,
document.getElementById('example')
);
實例解析:
1、我們可以使用函數定義了一個組件:
function HelloMessage(props) {
return <h1>Hello World!</h1>;
}
也可以使用 ES6 class 來定義一個組件:
class HelloMessage extends React.Component {
render() {
return <h1>Hello World!</h1>;
}
}
2、const element = <HelloMessage /> 爲用戶自定義的組件。
注意,原生 HTML 元素名以小寫字母開頭,而自定義的 React 類名以大寫字母開頭,比如 HelloMessage 不能寫成 helloMessage。除此之外還需要注意組件類只能包含一個頂層標籤,否則也會報錯。
如果我們需要向組件傳遞參數,可以使用 this.props 對象,實例如下:
function HelloMessage(props) {
return <h1>Hello {props.name}!</h1>;
}
const element = <HelloMessage name="Runoob"/>;
ReactDOM.render(
element,
document.getElementById('example')
);
注意,在添加屬性時, class 屬性需要寫成 className ,for 屬性需要寫成 htmlFor ,這是因爲 class 和 for 是 JavaScript 的保留字。
複合組件:
我們可以通過創建多個組件來合成一個組件,即把組件的不同功能點進行分離。
以下實例我們實現了輸出網站名字和網址的組件:
function Name(props) {
return <h1>網站名稱:{props.name}</h1>;
}
function Url(props) {
return <h1>網站地址:{props.url}</h1>;
}
function Nickname(props) {
return <h1>網站小名:{props.nickname}</h1>;
}
function App() {
return (
<div>
<Name name="菜鳥教程" />
<Url url="http://www.runoob.com" />
<Nickname nickname="Runoob" />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('example')
);
五、腳手架
npx create-react-app my-app
cd my-app
npm start
參考鏈接:https://facebook.github.io/create-react-app/docs/getting-started
在開發react應用時,應該沒有人用傳統的方法引入react的源文件(js),然後在html編輯吧。
大家都是用webpack + es6來結合react開發前端應用。
這個時候,我們可以手動使用npm來安裝各種插件,來從頭到尾自己搭建環境。
雖然自己搭建的過程也是一個很好的學習過程,但是有時候難免遇到各種問題,特別是初學者,而且每次開發一個新應用,都要自己從頭搭建,未免太繁瑣。
於是,有人根據自己的經驗和最佳實踐,開發了腳手架,避免開發過程中的重複造輪子和做無用功,從而節省開發時間。
create-react-app:https://github.com/facebook/create-react-app
六、快捷方式
VSC 安裝 ES7 React/Redux/GraphQL/React-Native snippets
rcc:
import React, { Component } from 'react'
export default class App extends Component {
render() {
return (
<div>
</div>
)
}
}
rcreudx:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
export class App extends Component {
static propTypes = {
prop: PropTypes
}
render() {
return (
<div>
</div>
)
}
}
const mapStateToProps = (state) => ({
})
const mapDispatchToProps = {
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
imrc:
import React, { Component } from 'react'
七、state和props
1、區別
props 是組件對外的接口,state 是組件對內的接口。組件內可以引用其他組件,組件之間的引用形成了一個樹狀結構(組件樹),如果下層組件需要使用上層組件的數據或方法,上層組件就可以通過下層組件的props屬性進行傳遞,因此props是組件對外的接口。組件除了使用上層組件傳遞的數據外,自身也可能需要維護管理數據,這就是組件對內的接口state。根據對外接口props 和對內接口state,組件計算出對應界面的UI。
主要區別:
- State是可變的,是一組用於反映組件UI變化的狀態集合;
- 而Props對於使用它的組件來說,是隻讀的,要想修改Props,只能通過該組件的父組件修改。在組件狀態上移的場景中,父組件正是通過子組件的Props, 傳遞給子組件其所需要的狀態。
React 的核心思想是組件化,而組件中最重要的概念是State(狀態),State是一個組件的UI數據模型,是組件渲染時的數據依據。
狀態(state) 和 屬性(props) 類似,都是一個組件所需要的一些數據集合,但是state是私有的,可以認爲state是組件的“私有屬性(或者是局部屬性)”。
2、用setState 修改State
直接修改state,組件並不會重新觸發render()
// 錯誤
this.state.comment = 'Hello';
正確的修改方式是使用setState()
// 正確
this.setState({comment: 'Hello'});
3、State 的更新是異步的
綜上所述:
this.props 和 this.state 可能是異步更新的,你不能依賴他們的值計算下一個state(狀態)
- 調用setState後,setState會把要修改的狀態放入一個隊列中(因而 組件的state並不會立即改變);
- 之後React 會優化真正的執行時機,來優化性能,所以優化過程中有可能會將多個 setState 的狀態修改合併爲一次狀態修改,因而state更新可能是異步的。
- 所以不要依賴當前的State,計算下個State。當真正執行狀態修改時,依賴的this.state並不能保證是最新的State,因爲React會把多次State的修改合併成一次,這時,this.state將還是這幾次State修改前的State。另外需要注意的事,同樣不能依賴當前的Props計算下個狀態,因爲Props一般也是從父組件的State中獲取,依然無法確定在組件狀態更新時的值。
八、組件
父子組件傳值,子組件調用父組件的方法
Control.js:
import React, {Component} from 'react'
class Control extends Component {
constructor(props) {
super(props)
}
handleAdd() {
this.props.onAdd()
}
render() {
return (
<div>
<button onClick={this.handleAdd.bind(this)}>加</button>
</div>
)
}
}
export default Control
Index.js:
import React, {Component} from 'react'
import Control from '../components/Control.js'
class Index extends Component {
constructor(props) {
super(props)
this.state = {
count: 0
}
}
handleAdd() {
let {count} = this.state
count = count + 1
this.setState({
count
})
}
render() {
let {count} = this.state
return (
<div>
<div>{count}</div>
<div>
<Control onAdd={this.handleAdd.bind(this)}/>
</div>
</div>
)
}
}
export default Index
九、todo list小練習
一個元素的 key 最好是這個元素在列表中擁有的一個獨一無二的字符串。通常,我們使用來自數據 id 來作爲元素的 key:
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
github地址:
動畫版todolist:
http://chenglou.github.io/react-motion/demos/demo3-todomvc-list-transition/
十、生命週期
常用的:
包括不常用的:
React v16.0前的生命週期:
參考鏈接:http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
文字敘述:https://zh-hans.reactjs.org/docs/react-component.html
實例期: componentWillMount render componentDidMount
存在期: componentWillReceiveProps shouldComponentUpdate componentWillUpdate render componentDidUpdate
銷燬期: componentWillUnmount
最新推出的生命週期:
getDerivedStateFromProps:
父組件:
import React, { Component } from 'react'
import Count from '../components/Count.js'
class Index extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
}
}
handleAdd() {
let {count} = this.state
count = count + 1
this.setState({
count
})
}
componentDidMount() {
}
render() {
let {count} = this.state
return (
<div>
<Count count={count}></Count>
<button onClick={this.handleAdd.bind(this)}>加</button>
</div>)
}
}
export default Index
子組件:
import React, {Component} from 'react'
class Count extends Component {
constructor(props) {
super(props)
this.state = {
count: 0
}
}
static getDerivedStateFromProps(props, state) {
return {
count: props.count
}
}
render() {
let { count } = this.state
return (
<div>
{count}
</div>
)
}
}
export default Count
getSnapshotBeforeUpdate
父組件:
import React, { Component } from 'react'
import ScrollingList from '../components/ScrollingList.js'
class Index extends Component {
constructor(props) {
super(props);
this.state = {
list:[]
}
}
handleAdd() {
let {list} = this.state
list.unshift({
id: (new Date()).getTime(),
text: 'xu' + (new Date()).getTime(),
})
this.setState({
list
})
}
render() {
let {
list
} = this.state
return (
<div>
<button onClick={this.handleAdd.bind(this)}>加</button>
<ScrollingList list={list}></ScrollingList>
</div>
);
}
}
export default Index
子組件:
import React, { Component } from 'react'
import './index.css'
class ScrollingList extends Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}
getSnapshotBeforeUpdate(prevProps, prevState) {
// 我們是否在 list 中添加新的 items ?
// 捕獲滾動位置以便我們稍後調整滾動位置。
if (prevProps.list.length < this.props.list.length || true) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// 如果我們 snapshot 有值,說明我們剛剛添加了新的 items,
// 調整滾動位置使得這些新 items 不會將舊的 items 推出視圖。
// (這裏的 snapshot 是 getSnapshotBeforeUpdate 的返回值)
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
//list.scrollTop = snapshot
}
}
render() {
let {
list
} = this.props
return (
<div>
<ul className="m-list-wrap" ref={this.listRef}>
{
list.map(item => (
<li key={item.id}>
{item.text}
</li>
))
}
</ul>
</div>
);
}
}
export default ScrollingList
十一、路由
參考鏈接:https://reacttraining.com/react-router/web/api/BrowserRouter
常用api有:
BrowserRouter:包裹根組件
Switch: 有<Switch>標籤,則其中的<Route>在路徑相同的情況下,只匹配第一個,這個可以避免重複匹配
Route:路由
Link :鏈接
withRouter:包裹組件,包裹後組件的props會增加三個屬性: match, location, history
1)通過js跳轉路由:this.props.history.push('/tasklist')
2)獲取動態路由參數
let { match } = this.props
if (match.params.new === 'new') {
}
3)獲取路徑名:<div>{this.props.location.pathname}</div>
首先加上了Switch,其次加入了exact。加入Switch表示,只顯示一個組件。加exact表示精確匹配/
嵌套路由,從廣義上來說,分爲兩種情況:一種是每個路由到的組件都有共有的內容,這時把共有的內容抽離成一個組件,變化的內容也是一個組件,兩種組件組合嵌套,形成一個新的組件。另一種是子路由,路由到的組件內部還有路由。
對於共有的內容,典型的代表就是網頁的側邊欄,假設側邊欄在左邊,我們點擊其中的按鈕時,右側的內容會變化,但不管右側的內容怎麼變化,左側的側邊欄始終存在。
BrowserRouter 和 HashRouter 都可以實現前端路由的功能,區別是前者基於url的pathname段,後者基於hash段。
前者:http://127.0.0.1:3000/article/num1
後者:http://127.0.0.1:3000/#/article/num1(不一定是這樣,但#是少不了的)
這樣的區別帶來的直接問題就是當處於二級或多級路由狀態時,刷新頁面,前者會將當前路由發送到服務器(因爲是pathname),而後者不會(因爲是hash段)。
多級路由:
使用react-router-dom router.js:
import React, { Component } from 'react'
import { Switch, Route, Redirect } from 'react-router-dom'
import Login from '../pages/Login.js'
import Header from '../components/Header.js'
import Sidebar from '../components/Sidebar.js'
import CityNav from '../components/CityNav.js'
import CityNavForShanDong from '../components/CityNavForShanDong.js'
import DistrictNav from '../components/DistrictNav.js'
import ShanXi from '../pages/province/ShanXi.js'
import TangShan from '../pages/province/city/TangShan.js'
import QinHuangDao from '../pages/province/city/QinHuangDao.js'
import JiNan from '../pages/province/city/JiNan.js'
import QingDao from '../pages/province/city/QingDao.js'
import YanTai from '../pages/province/city/YanTai.js'
import ChangAn from '../pages/province/city/district/ChangAn.js'
import QiaoXi from '../pages/province/city/district/QiaoXi.js'
import XinHua from '../pages/province/city/district/XinHua.js'
import './index.css'
class Router extends Component {
render() {
return (
<div>
<Switch>
<Route path='/' exact={true} component={Login}></Route>
<Route path='/index'>
<Route>
<Header></Header>
<Sidebar></Sidebar>
</Route>
<Switch>
<Route path="/index/hebei">
<Route>
<CityNav></CityNav>
</Route>
<Switch>
<Route path="/index/hebei/shijiazhuang">
<Route>
<DistrictNav></DistrictNav>
</Route>
<Switch>
<Route path="/index/hebei/shijiazhuang/changan" component={ChangAn}></Route>
<Route path="/index/hebei/shijiazhuang/qiaoxi" component={QiaoXi}></Route>
<Route path="/index/hebei/shijiazhuang/xinhua" component={XinHua}></Route>
</Switch>
</Route>
<Route path="/index/hebei/tangshan" component={TangShan}></Route>
<Route path="/index/hebei/qinhuangdao" component={QinHuangDao}></Route>
</Switch>
</Route>
<Route path="/index/shandong">
<Route>
<CityNavForShanDong></CityNavForShanDong>
</Route>
<Route path="/index/shandong/jinan" component={JiNan}></Route>
<Route path="/index/shandong/qingdao" component={QingDao}></Route>
<Route path="/index/shandong/yantai" component={YanTai}></Route>
</Route>
<Route path="/index/shanxi" component={ShanXi}></Route>
</Switch>
</Route>
</Switch>
</div>
)
}
}
export default Router
github源碼(使用react-router-dom):
使用react-router-config routerConfig.js:
import React from 'react';
import { renderRoutes } from "react-router-config";
import Login from '../pages/Login.js'
import Header from '../components/Header.js'
import Sidebar from '../components/Sidebar.js'
import CityNav from '../components/CityNav.js'
import CityNavForShanDong from '../components/CityNavForShanDong.js'
import DistrictNav from '../components/DistrictNav.js'
import ShanXi from '../pages/province/ShanXi.js'
import TangShan from '../pages/province/city/TangShan.js'
import QinHuangDao from '../pages/province/city/QinHuangDao.js'
import JiNan from '../pages/province/city/JiNan.js'
import QingDao from '../pages/province/city/QingDao.js'
import YanTai from '../pages/province/city/YanTai.js'
import ChangAn from '../pages/province/city/district/ChangAn.js'
import QiaoXi from '../pages/province/city/district/QiaoXi.js'
import XinHua from '../pages/province/city/district/XinHua.js'
const Root = ({ route }) => (
<div>
{renderRoutes(route.routes)}
</div>
);
const Index = ({ route }) => (
<div>
<Header></Header>
<Sidebar></Sidebar>
{renderRoutes(route.routes)}
</div>
);
const HeBei = ({ route }) => (
<div>
<CityNav></CityNav>
{renderRoutes(route.routes)}
</div>
);
const ShiJiaZhuang = ({ route }) => (
<div>
<DistrictNav></DistrictNav>
{renderRoutes(route.routes)}
</div>
);
const ShanDong = ({ route }) => (
<div>
<CityNavForShanDong></CityNavForShanDong>
{renderRoutes(route.routes)}
</div>
);
const routes = [
{
component: Root,
routes: [
{
path: "/",
exact: true,
component: Login
},
{
path: "/index",
component: Index,
routes: [
{
path: "/index/hebei",
component: HeBei,
routes: [
{
path: "/index/hebei/shijiazhuang",
component: ShiJiaZhuang,
routes: [
{
path: '/index/hebei/shijiazhuang/changan',
component: ChangAn,
},
{
path: '/index/hebei/shijiazhuang/qiaoxi',
component: QiaoXi,
},
{
path: '/index/hebei/shijiazhuang/xinhua',
component: XinHua,
}
]
},
{
path: "/index/hebei/tangshan",
component: TangShan
},
{
path: "/index/hebei/qinhuangdao",
component: QinHuangDao,
}
]
},
{
path: "/index/shandong",
component: ShanDong,
routes: [
{
path: "/index/shandong/jinan",
component: JiNan
},
{
path: "/index/shandong/qingdao",
component: QingDao
},
{
path: "/index/shandong/yantai",
component: YanTai
}
]
},
{
path: "/index/shanxi",
component: ShanXi
}
]
}
]
}
];
export default routes
github地址(使用react-router-coinfg):
拆分後更好維護:
import React, { Component } from 'react'
import { Switch, Route, Redirect } from 'react-router-dom'
import Login from '../pages/Login'
import ShanDong from '../pages/ShanDong'
import ShanXi from '../pages/ShanXi'
import ShiJiaZhuang from '../pages/ShiJiaZhuang'
import ZhangJiaKou from '../pages/ZhangJiaKou'
import ChengDe from '../pages/ChengDe'
import NoFound from '../pages/NoFound'
import Header from '../components/Header'
import Sidebar from '../components/Sidebar'
import HeBeiCity from '../components/HeBeiCity'
import './index.css'
const HeBei = () => {
return (
<>
<Route>
<HeBeiCity></HeBeiCity>
</Route>
<Route path="/index/hebei/shijiazhuang" component={ShiJiaZhuang}></Route>
<Route path="/index/hebei/zhangjiakou" component={ZhangJiaKou}></Route>
<Route path="/index/hebei/chengde" component={ChengDe}></Route>
</>
)
}
const Index = () => {
return (
<>
<Route>
<Header></Header>
<Sidebar></Sidebar>
</Route>
<Switch>
<Route path="/index/hebei" component={HeBei}></Route>
<Route path="/index/shandong" component={ShanDong}></Route>
<Route path="/index/shanxi" component={ShanXi}></Route>
</Switch>
</>
)
}
export default class Router extends Component {
render() {
return (
<>
<Switch>
<Redirect exact from="/" to="/login"></Redirect>
<Redirect exact from="/index/hebei" to="/index/hebei/shijiazhuang"></Redirect>
<Route path="/login" component={Login}></Route>
<Route path="/index" component={Index}></Route>
<Route component={NoFound}></Route>
</Switch>
</>
)
}
}
github源代碼:
十二、使用 PropTypes 進行類型檢查
參考鏈接:https://zh-hans.reactjs.org/docs/typechecking-with-proptypes.html#___gatsby
十三、refs 和DOM
參考鏈接:https://zh-hans.reactjs.org/docs/refs-and-the-dom.html#___gatsby
ref添加到Component上獲取的是Component實例,添加到原生HTML上獲取的是DOM
第一種用法(createRef):
import React, { Component } from 'react'
class MyComponent extends Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
handleFocus() {
this.myRef.current.focus()
}
componentDidMount() {
this.myRef.current.focus()
}
render() {
return (
<div>
<input ref={this.myRef} />
<button onClick={this.handleFocus.bind(this)}>獲取焦點</button>
</div>)
}
}
export default MyComponent
第二種寫法(回調函數):
import React, { Component } from 'react'
class MyComponent extends Component {
constructor(props) {
super(props);
}
handleFocus() {
this.input.focus()
}
setInputRef(element) {
this.input = element
}
componentDidMount() {
this.input.focus()
}
render() {
return (
<div>
<input ref={this.setInputRef.bind(this)} />
<button onClick={this.handleFocus.bind(this)}>獲取焦點</button>
</div>)
}
}
export default MyComponent
第三種寫法(字符串,過時 API,不要再使用):
import React, { Component } from 'react'
class MyComponent extends Component {
constructor(props) {
super(props);
}
handleFocus() {
this.refs.input.focus()
}
componentDidMount() {
this.refs.input.focus()
}
render() {
return (
<div>
<input ref='input' />
<button onClick={this.handleFocus.bind(this)}>獲取焦點</button>
</div>)
}
}
export default MyComponent
第四種寫法(子組件傳遞ref給父組件):
父組件:
import React, { Component } from 'react'
import Input from '../components/Input.js'
class Index extends Component {
constructor(props) {
super(props);
}
handleFocus() {
this.input.focus()
}
handleSetInputRef(element) {
this.input = element
}
componentDidMount() {
this.input.focus()
}
render() {
return (
<div>
<Input onInputRef={this.handleSetInputRef.bind(this)}></Input>
<button onClick={this.handleFocus.bind(this)}>獲取焦點</button>
</div>)
}
}
export default Index
子組件:
import React, {Component} from 'react'
class Input extends Component {
render() {
return (
<div>
<input ref={this.props.onInputRef.bind(this)}></input>
</div>
)
}
}
export default Input
十四、ReactDOM
findDOMNode是ReactDOM中的方法
ReactDOM.findDOMNode(this)返回的是組件實例相對應的DOM節點
import React, { Component } from 'react'
import ReactDOM from 'react-dom';
class Index extends Component {
constructor(props) {
super(props);
}
handleClick() {
let dom = ReactDOM.findDOMNode(this)
console.log(dom)
}
componentDidMount() {
}
render() {
return (
<div>
<button onClick={this.handleClick.bind(this)}>按鈕</button>
</div>)
}
}
export default Index
findDOMNode當參數是DOM,返回值就是該DOM:
import React, { Component } from 'react'
import ReactDOM from 'react-dom';
class Index extends Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
handleClick() {
let dom = ReactDOM.findDOMNode(this.myRef.current)
console.log(dom)
}
componentDidMount() {
}
render() {
return (
<div>
<input ref={this.myRef} />
<button onClick={this.handleClick.bind(this)}>按鈕</button>
</div>)
}
}
export default Index
find
十五、Mock數據
參考鏈接:https://github.com/nuysoft/Mock/wiki/Getting-Started
十六、antd
參考鏈接:https://ant.design/docs/react/introduce-cn
十七、Context
參考鏈接:https://zh-hans.reactjs.org/docs/context.html#___gatsby
index.js:
import React, {Component} from 'react'
import {ThemeContext} from './theme-context';
import Toolbar from '../components/Toolbar.js'
class Index extends React.Component {
constructor(props) {
super(props)
this.state = {
count: 0
}
}
handleAdd() {
let {count} = this.state
count = count + 1
this.setState({
count
})
}
render() {
let {count} = this.state
return (
<div>
<ThemeContext.Provider value={count}>
<Toolbar/>
<button onClick={this.handleAdd.bind(this)}>加</button>
</ThemeContext.Provider>
</div>
);
}
}
export default Index
theme-context.js:
import React from 'react'
export const ThemeContext = React.createContext('light');
Tootbar.js:
import React, {Component} from 'react'
import ThemedButton from './ThemedButton.js'
class Toolbar extends Component {
render() {
return (
<ThemedButton/>
)
}
}
export default Toolbar
ThemedButton.js:
import React, {Component} from 'react'
import {ThemeContext} from '../pages/theme-context';
class ThemedButton extends Component {
render() {
let value = this.context
return (
<div>{value}</div>
)
}
}
ThemedButton.contextType = ThemeContext;
export default ThemedButton
十八、事件系統
Virtual DOM在內存中是以對象的形式存在,如果想要在這些對象上加事件就會比較簡單。React基於Virtual DOM實現了一個合成事件層,我們所定義的事件會接受到一個合成事件對象的實例。不會存在IE瀏覽器兼容性的問題,同樣支持事件冒泡機制。
React事件的綁定方式在寫法上與原生HTML事件監聽很相似:
<button onClick={this.handleClick.bind(this)}></button>
這個和JavaScript的DOM0級事件很像,但是又有一些不同,下面是DOM0級事件:
<button onclick="handle()"></button>
React並不會像DOM0級事件那樣將事件處理器直接綁定到DOM上,React僅僅是借鑑了這種寫法。
在React底層,主要對合成事件做了兩件事情:事件委派和自動綁定。
1. 事件委派
React中並不是把事件處理函數綁定到當前DOM上,而是把所有的事件綁定到結構的最外層,使用統一的事件監聽器。
這個事件監聽器上維持了一個映射來保存所有組件內部的事件監聽和處理函數。
組件掛載和卸載時,只是在統一事件監聽器上插入刪除一些對象。
2. 自動綁定
在React組件中,每個方法的上下文都會指向該組件的實例,即自動綁定this爲當前的組件。而且React會對這種引用緩存,以達到CPU和內存的最大優化。
注意:在使用es6 class或者純函數的寫法,這種綁定不復存在,我們需要手動實現this綁定。
bind方法:該方法可以幫助我們綁定事件處理器內的this,並可以向事件處理器中傳入參數:
class App extends Component {
handleClick(e, arg) {
console.log(e, arg)
}
render() {
return <button onClick={this.handleClick.bind(this, 'test')}></button>
}
}
十九、Redux
解決的問題:
原理:
動畫:
二十、redux-thunk
源代碼:
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
編譯成es5:
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
function createThunkMiddleware(extraArgument) {
return function (_ref) {
var dispatch = _ref.dispatch,
getState = _ref.getState;
return function (next) {
return function (action) {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
};
};
}
var thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
var _default = thunk;
exports.default = _default;
Redux的核心概念其實很簡單:將需要修改的state都存入到store裏,發起一個action用來描述發生了什麼,用reducers描述action如何改變state tree 。創建store的時候需要傳入reducer,真正能改變store中數據的是store.dispatch API。
dispatch一個action之後,到達reducer之前,進行一些額外的操作,就需要用到middleware。你可以利用 Redux middleware 來進行日誌記錄、創建崩潰報告、調用異步接口或者路由等等。換言之,中間件都是對store.dispatch()的增強。
中間件的用法:
import { createStore, combineReducers, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import reducers from './reducers'
const reducer = combineReducers(reducers)
const store = createStore(reducer, applyMiddleware(thunk))
export default store
直接將thunk中間件引入,放在applyMiddleware方法之中,傳入createStore方法,就完成了store.dispatch()的功能增強。即可以在reducer中進行一些異步的操作。
其實applyMiddleware就是Redux的一個原生方法,將所有中間件組成一個數組,依次執行。
手寫中間件(logger,thunk) :
import { createStore, combineReducers, applyMiddleware } from 'redux'
import reducers from './reducers'
const logger = store => next => action => {
console.log('prev state', store.getState())
console.log('dispatch', action)
next(action)
console.log('next state', store.getState())
}
const logger2 = store => next => action => {
console.log('prev state2', store.getState())
console.log('dispatch2', action)
next(action)
console.log('next state2', store.getState())
}
const myThunk = store => next => action => {
if (typeof action === 'function') {
return action(store.dispatch)
}
return next(action)
}
const reducer = combineReducers(reducers)
const store = createStore(reducer, applyMiddleware(myThunk, logger, logger2))
export default store
參考鏈接:https://segmentfault.com/a/1190000018347235
二十一、flex三段式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, width=device-width">
<title>Document</title>
<style type="text/css">
html {
height: 100%;
}
body {
margin: 0;
height: 100%;
}
.m-wrap {
display: flex;
flex-direction: column;
height: 100%;
}
.m-header {
height: 50px;
background: #aaaaaa;
}
.m-content {
flex: 1;
background: #dddddd;
}
.m-footer {
height: 50px;
background: #cccccc;
}
</style>
</head>
<body>
<div class="m-wrap">
<header class="m-header">head</header>
<main class="m-content">main</main>
<footer class="m-footer">footer</footer>
</div>
</body>
</body>
</html>
二十二、小米書城
github地址:
二十三、噹噹網
github地址:
https://github.com/baweireact/m-react-demo-base/tree/master/30%E5%BD%93%E5%BD%93%E7%BD%91
二十四、輪播圖
github地址:
二十五、登錄和組件嵌套
github地址:
二十六、貓眼
github地址:
https://github.com/baweireact/m-react-demo-base/tree/master/31%E7%8C%AB%E7%9C%BC
二十七、購物車
github地址:
其他
forceUpdate方法能使組件調用自身的render()方法重新渲染組件:
handleUpate() {
this.forceUpdate()
}
reactDom.unmountComponentAtNode()方法裏接收指定的容器參數:
從 DOM 中卸載組件,會將其事件處理器(event handlers)和 state 一併清除。如果指定容器上沒有對應已掛載的組件,這個函數什麼也不會做。如果組件被移除將會返回 true
,如果沒有組件可被移除將會返回 false
。
handleUnmount() {
ReactDOM.unmountComponentAtNode(document.getElementById('root'))
}
componentWillUnmount() {
console.log('卸載前')
}
組件裏面綁定的合成事件想要通過 e.stopPropagation() 來阻止事件冒泡到 document如何解決
e.nativeEvent.stopImmediatePropagation()