
前言:篇幅較長,需要耗費一些時間和精力哈??
起步
npx create-react-app my-app // 1.創(chuàng)建項目
cd my-app // 2.打開項目
npm start // 3.啟動項目
npm run eject // 4.暴露配置項
目錄
├── README.md ?檔
├── public 靜態(tài)資源
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
└── src 源碼
├── App.css
├── App.js 根組件
├── App.test.js
├── index.css 全局樣式
├── index.js ???件
├── logo.svg
└── serviceWorker.js pwa?持
├── package.json npm 依賴
入口文件定義
webpack.config.js
React和ReactDom
import React from 'react'
import ReactDom from 'react-dom'
// JSX => React.createElement(...)
ReactDom.render(<h2>Hello React</h2>,document.querySelector('#root'))
React:負責(zé)邏輯控制,數(shù)據(jù) -> VDOM
ReactDOM:渲染實際DOM,VDOM -> DOM
React使用JSX描述UI
babel-loader把JSX編譯成相應(yīng)的JS對象,React.createElement再把這個JS對象構(gòu)造成React需要的虛擬dom
JSX
JSX是JavaScript的一種語法擴展,其格式比較像模板語言,但事實上完全是在JavaScript內(nèi)部實現(xiàn)的。JSX可以很好的描述UI,能夠有效提高開發(fā)效率。
- 基本使用
// 表達式{}的使用
const name = 'Cherry'
const jsx = <div>hello,{name}</div>
React.render(jsx,document.querySelector('#root'))
- 函數(shù)
// 函數(shù)也是合法表達式
const obj = {
firstName: 'Harry',
lastName: 'Potter'
}
function formatName(name) {
return name.firstName + '-' + name.lastName
}
const jsx = <div>hello ,{formatName(obj)}</div>
- 對象
// jsx是js對象,也是合法表達式
const greet = <div>Cherry</div>
const jsx = (
<div>
<div>hello</div>
{greet}
</div>
)
- 條件語句
const show = false;
const greet = <div>Cherry</div>
const jsx = (
<div>
{show ? greet : '登錄'}
{show && greet}
</div>
)
- 數(shù)組
// 數(shù)組會被當(dāng)作一組元素對待,數(shù)組中存放一組jsx可用于顯示列表數(shù)據(jù)
const arr = [1,2,3]
const jsx = (
<ul>
// diff的時候,會先比較type類型,然后是key,所以同級同類型元素,key值必須唯一
{arr.map(item => (<li key={item}>{item}</li>))}
</ul>
)
- 屬性
import logo from './logo.png';
const jsx = (
<div>
// 靜態(tài)值用雙引號,動態(tài)值用花括號;class、for等要特殊處理
<img src={logo} className="logo" style={{width:100,height:100}} />
</div>
)
- 模塊化
// index.js
// 命名空間 css模塊化,創(chuàng)建index.module.css
import style from './index.module.css'
const jsx = (
<div className=style.logo>Pink</div>
)
// index.module.css
.logo {
width: 100px;
height: 50px;
color: pink;
}
組件
組件,從概念上類似于JavaScript函數(shù)。它接受任意的入?yún)ⅲ础皃rops”),并返回用于描述頁面展示內(nèi)容的React元素。
組件有兩種形式:class組件和function組件
- class組件
class組件通常擁有狀態(tài)和生命周期,繼承于Component,實現(xiàn)render方法,下面我們用class組件來寫一個小例子
// ClassComponent.js
// 導(dǎo)入React以及成員組件Component
import React, { Component } from 'react'
export default class ClassComponent extends Component {
constructor(props) {
super(props)
this.state = {
date: new Date()
}
}
componentDidMount() {
// 組件掛載之后啟動定時器每秒更新狀態(tài)
this.timer = setInterval(() => {
// 使用setState來更新狀態(tài)
this.setState({
date: new Date()
})
},1000)
}
componentWillUnmount() {
// 組件卸載前停?定時器
clearInterval(this.timer)
}
render() {
return (
<div>{this.state.date.toLocaleTimeString()}</div>
)
}
}
- function組件
函數(shù)組件通常無狀態(tài),僅關(guān)注內(nèi)容展示,返回渲染結(jié)果即可。
從React16.8開始引入了hooks,函數(shù)組件也能夠擁有狀態(tài)。
// FunctionComponent.js
import React, {useState, useEffect} from 'react'
export function FunctionComponent(props) {
const [date,setDate] = useState(new Date())
// 相當(dāng)于 componentDidMount、componentDidUpdate、componentWillUnmount的集合
useEffect(() => { // 副作用
const timer = setInterval(() => {
setDate(new Date())
},1000)
return () => clearInterval(timer) // 組件卸載時執(zhí)行
},[])
return (
<div>
{date.toLocalTimeString()}
</div>
)
}
setState的使用
setState(partialState, callback)
1.partialState: object|function 用于產(chǎn)生與當(dāng)前state合并的子集
2.callback : function state更新之后被調(diào)?。
import的使用(拐個彎??)
import語法引用模塊時,如何正確使用{}
現(xiàn)在有兩個模塊分別為A.js,B.js
- import不使用花括號
// A.js
export default 88
// B.js
import A from './A' // 只有在如上A.js中有默認導(dǎo)出的export default語法時才會生效
// 不使用{}來引用模塊的情況下,import模塊時的命名是隨意的,如下:
import A from './A'
import sameA from './A'
import xxx from './A'
// Because 它總是會解析到A.js中默認的export default
- import 使用花括號
// A.js
export const A = 88
// B.js
import { A } from './A' // 只有在如上模塊A.js中有命名導(dǎo)出為A的export name的代碼
import { A } from './A' // 成功引入,因為A.js中有命名為A的export
import { sameA } from './A' // 引入失敗,因為A.js中沒有命名為sameA的export
import { xxx } from './A' // 同上
// Because 明確聲明了命名導(dǎo)出后,在另一個js中使用{}引入模塊時,import時的模塊命名是有意義的
// 上述代碼正確執(zhí)行,如下
// A.js
export const A = 88
export const sameA = 99
export const xxx = xxx
??Warning??: 一個模塊只能有一個默認導(dǎo)出export default,但是可以任意命名導(dǎo)入很多次,也可以一次性都導(dǎo)入,例如:
// B.js
import A, { sameA, xxx } from './A'
// 這里我們使用導(dǎo)入默認導(dǎo)出A,以及命名導(dǎo)出sameA和xxx
// 我們也可以在導(dǎo)入的時候重命名導(dǎo)入
import B, {sameA as sameB, xxx as bbb} from './A'
??? 小結(jié)一下:模塊的默認導(dǎo)出通常用在導(dǎo)入整個模塊的內(nèi)容,而命名導(dǎo)出用于導(dǎo)入一些有用的公共方法
生命周期
生命周期方法,用于組件不同階段執(zhí)行自定義功能。在組件被創(chuàng)建并插入到DOM時(即掛載中階段mounting),組件更新時,組件取消掛載或從DOM中刪除時,都有可以使用的生命周期方法
- React V16.3之前的生命周期
// LifeCyclePage.js
import React, { Component } from "react";
import PropTypes from "prop-types";
export default class LifeCyclePage extends Component {
static defaultProps = {
// msg: "hello",
};
static propTypes = {
// msg: PropTypes.string.isRequired,
};
constructor(props) {
super(props);
this.state = {
count: 0,
};
console.log("constructor");
}
componentWillMount() {
console.log("componentWillMount");
}
componentDidMount() {
console.log("componentDidMount");
}
shouldComponentUpdate(nextProps, nextState) {
const { count } = nextState;
console.log("shouldComponentUpdate", nextState);
// return true 執(zhí)行更新操作,return false 返回組件運行時
return count !== 3;
}
componentWillUpdate() {
console.log("willComponentUpdate");
}
componentDidUpdate() {
console.log("componentDidUpdate");
}
setCount = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
console.log("render", this.props);
const { count } = this.state;
return (
<div>
<h3>Lifecycle</h3>
<h4>{count}</h4>
<button onClick={this.setCount}>改變count</button>
// {count % 2 && <Child count={count} />}
<Child count={count} />
</div>
);
}
}
class Child extends Component {
componentWillReceiveProps(nextProps) {
console.log("componentWillReceiveProps", nextProps);
}
componentWillUnmount() {
console.log("componentWillUnmount");
}
render() {
console.log("Child render");
const { count } = this.props;
return (
<div>
<h3>Child</h3>
<p>{count}</p>
</div>
);
}
}
- shouldComponentUpdate -> return true
執(zhí)行順序: constructor => componentWillMount => render => componentDidMount => shouldComponentUpdate => willComponentUpdate => render => componentDidUpdate => over??
?? componentWillReceiveProps(初次渲染的時候不執(zhí)行,只有在已掛載的組件接收新的props的時候,才執(zhí)行)
?? componentWillUnmount(組件卸載之前,常用在清除定時器等等...)- shouldComponentUpdate -> return false
執(zhí)行順序:constructor => componentWillMount => render => componentDidMount => shouldComponentUpdate => over??
- React V16.4之后的生命周期
打開控制臺的Warning,如下
Warning.png
V17可能被廢棄的三個生命周期函數(shù)用getDerivedStateFromProps替代,目前使用的話加上UNSAFE_:
?? componentWillMount
?? componentWillReceiveProps
?? componentWillUpdate
引入兩個新的生命周期函數(shù):
?? static getDerivedStateFromProps
?? getSnapshotBeforeUpdate
如果不想手動給將要廢棄的生命周期加上UNSAFE_前綴,可以用以下命令:
npx react-codemod rename-unsafe-lifecycles <path>
新引入的兩個生命周期函數(shù)
- getDerivedStateFromProps
static getDerivedStateFromProps(props,state)
這個生命周期函數(shù)會在調(diào)用render方法之前調(diào)用,并且在初始掛載及后續(xù)更新時都會被調(diào)用。它應(yīng)返回一個對象來更新state,如果返回null則不更新任何內(nèi)容。
??Warning??:不管原因是什么,都會在每次渲染前觸發(fā)此方法。這與UNSAFE_componentWillReceiveProps形成對比,后者僅在父組件重新渲染時觸發(fā),而不是在內(nèi)部調(diào)用setState時。
- getSnapshotBeforeUpdate
getSnapshotBeforeUpdate(prevProps,prevState)
在render之后,在componentDidUpdate之前
這個生命周期函數(shù)在最近一次渲染輸出(提交到DOM節(jié)點)之前調(diào)用。它使得組件能在發(fā)生更改之前從DOM中捕獲一些信息(例如,滾動位置)。此生命周期的任何返回值將作為參數(shù)傳遞給componentDidUpdate(prevProps,prevState,snapshot)
接下來我們看一下執(zhí)行順序
import React, { Component } from "react";
import PropTypes from "prop-types";
export default class LifeCyclePage extends Component {
static defaultProps = {
// msg: "hello",
};
static propTypes = {
// msg: PropTypes.string.isRequired,
};
constructor(props) {
super(props);
this.state = {
count: 0,
};
console.log("constructor");
}
// 在render之后,在componentDidUpdate之前
getSnapshotBeforeUpdate(prevProps, prevState, snapshot) {
console.log("getSnapshotBeforeUpdate", prevProps, prevState, snapshot);
// return null;
return {
msg: "hello,我是getSnapshotBeforeUpdate",
};
}
// 在調(diào)用render方法之前調(diào)用,并且在初始掛載及后續(xù)更新時都會被調(diào)用
static getDerivedStateFromProps(props, state) {
console.log("getDerivedStateFromProps");
// return null; // 不更新任何內(nèi)容
const { count } = state;
return count > 5 ? { count: 0 } : null; // 更新state
}
// UNSAFE_componentWillMount() {
// console.log("componentWillMount");
// }
componentDidMount() {
console.log("componentDidMount");
}
shouldComponentUpdate(nextProps, nextState) {
const { count } = nextState;
console.log("shouldComponentUpdate", nextState);
// return false;
return true;
}
// UNSAFE_componentWillUpdate() {
// console.log("willComponentUpdate");
// }
componentDidUpdate(prevProps, prevState, snapshot) {
console.log("componentDidUpdate", prevProps, prevState, snapshot);
}
setCount = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
console.log("render", this.props);
const { count } = this.state;
return (
<div>
<h3>Lifecycle</h3>
<h4>{count}</h4>
<button onClick={this.setCount}>改變count</button>
{/* {count % 2 && <Child count={count} />} */}
<Child count={count} />
</div>
);
}
}
class Child extends Component {
// UNSAFE_componentWillReceiveProps(nextProps) {
// console.log("componentWillReceiveProps", nextProps);
// }
componentWillUnmount() {
console.log("componentWillUnmount");
}
render() {
console.log("Child render");
const { count } = this.props;
return (
<div>
<h3>Child</h3>
<p>{count}</p>
</div>
);
}
}
組件復(fù)合
組件復(fù)合可以非常敏捷的自定義組件的外觀和行為,這種方式更明確和安全。如果組件之間有公用的非UI邏輯,應(yīng)該將他們抽取為js模塊導(dǎo)入使用而不繼承。日常開發(fā)中我們經(jīng)常會涉及到組件復(fù)用的例子,例如頭部和底部復(fù)用,下面是一個小的demo??
// HomePage.js
import React, { Component } from "react";
import Layout from "./Layout";
export default class HomePage extends Component {
render() {
return (
// showTopBar -> 是否展示頂部欄
// showBottomBar -> 是否展示底部欄
// title -> 標題
<Layout showTopBar={false} showBottomBar={true} title={"我的商城"}>
{/* <div>
<h3>HomePage</h3>
</div> */}
// 具名插槽
{{
content: (
<div>
<h3>HomePage</h3>
</div>
), // 通過jsx寫頁面內(nèi)容
txt: "這是一個文本", // 傳文本
btnClick: () => console.log("call me"), // 傳方法
}}
</Layout>
);
}
}
// Layout.js
import React, { Component } from "react";
import TopBar from "../components/TopBar"; // 頂部欄組件
import BottomBar from "../components/BottomBar"; // 底部欄組件
export default class Layout extends Component {
componentDidMount() {
const { title = "商城" } = this.props;
document.title = title;
}
render() {
const { children, showTopBar, showBottomBar } = this.props;
console.log("children", children);
return (
<div>
{showTopBar && <TopBar />}
<h3>{children.content}</h3>
{showBottomBar && <BottomBar />}
<h5>{children.txt}</h5>
<button onClick={children.btnClick}>點我</button>
</div>
);
}
}
Redux
Redux是負責(zé)組織state的工具,提供可預(yù)測的狀態(tài)管理,不僅僅是用于React。下面我們就來了解一下什么場景下使用redux,redux應(yīng)該怎么使用????
- ?? 使用場景
- 有著相當(dāng)大量的、隨著時間變化的數(shù)據(jù);
- state需要有一個單一可靠數(shù)據(jù)來源;
- 把所有state放在最頂層組件中無法滿足需求;
- 某個組件的狀態(tài)需要共享...
- ?? 如何使用
安裝Redux
npm install redux --save
??累加器??
- 需要一個store來存儲數(shù)據(jù);
- store里的reducer初始化state并定義state修改規(guī)則;
- 通過dispatch一個action來提交對數(shù)據(jù)的更改;
- action提交到reducer函數(shù)里,根據(jù)傳入的action的type,返回新的state
創(chuàng)建store,src/store/ReduxStore.js
import { createStore } from "redux";
// 定義state初始化和修改規(guī)則,reducer是一個純函數(shù)
function counterReducer(state = 0, action) {
// console.log("state", state);
switch (action.type) {
case "ADD":
return state + 1;
case "MINUS":
return state - 1;
default:
return state;
}
}
const store = createStore(counterReducer);
export default store;
創(chuàng)建ReduxPage
import React, { Component } from "react";
import store from "../store/";
export default class ReduxPage extends Component {
componentDidMount() {
// 訂閱狀態(tài)變更
// store.subscribe(() => {
// console.log("state發(fā)生變化了");
// this.forceUpdate(); // 強制刷新
// });
}
render() {
console.log("store", store);
return (
<div>
<h3>ReduxPage</h3>
<p>{store.getState()}</p>
<button
onClick={() => {
store.dispatch({ type: "ADD" });
}}
>
add
</button>
</div>
);
}
}
在src/index.js的render里訂閱狀態(tài)變更
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import store from "./store";
ReactDOM.render(<App />, document.getElementById("root"));
store.subscribe(() => {
console.log("state更新了");
ReactDOM.render(<App />, document.getElementById("root"));
});
??小結(jié)一下??
- 通過createStore創(chuàng)建store
- reducer初始化、修改狀態(tài)函數(shù)
- getState 獲取狀態(tài)值
- dispatch 提交更新
- subscribe 變更訂閱
react-redux
react-redux從名字上來看就是為react量身定做的,接下來我們來了解一下如何安裝和如何使用
react-redux
- 安裝
npm install react-redux --save
- 使用
react-redux提供來兩個api:
?? 1.Provider 為后代組件提供store
?? 2. connect 為組件提供數(shù)據(jù)和變更方法
全局提供store,index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import store from "./store";
import { Provider } from "react-redux";
ReactDOM.render(
// 通過Provider跨層級傳遞把store傳給后代
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
獲取狀態(tài)數(shù)據(jù),ReactReduxPage.js
import React, { Component } from "react";
import { connect } from "react-redux";
export default connect(
// mapStateToProps 狀態(tài)映射 把state映射到props
(state) => ({ num: state }),
// mapDispatchToProps 派發(fā)事件映射 把dispatch映射到props)
{
add: () => ({ type: "ADD" }),
}
)(
class ReactReduxPage extends Component {
render() {
const { num, dispatch, add } = this.props;
console.log("props", this.props);
return (
<div>
<h3>{num}</h3>
<h3>ReactReduxPage</h3>
{/* <button onClick={() => dispatch({ type: "ADD" })}>add</button> */}
{/* 方法比較多的時候*/}
<button onClick={add}>add</button>
</div>
);
}
}
);
?? 另一種寫法
class ReactReduxPage extends Component {
render() {
const { num, add, minus } = this.props;
return (
<div>
<h3>{num}</h3>
<h3>ReactReduxPage</h3>
<button onClick={minus}>minus</button>
<button onClick={add}>add</button>
</div>
);
}
}
// mapStateToProps 狀態(tài)映射 把state映射到props
const mapStateToProps = (state) => {
return {
num: state,
};
};
// mapDispatchToProps 派發(fā)事件映射 把dispatch映射到props)
const mapDispatchToProps = {
add: () => {
return { type: "ADD" };
},
minus: () => {
return { type: "MINUS" };
},
};
// connect中的參數(shù):state映射和事件映射
export default connect(mapStateToProps, mapDispatchToProps)(ReactReduxPage);
react-router
react-router包含三個庫,分別
react-router、react-router-dom和react-native。
?? react-router提供最基本的路由功能,實際應(yīng)用我們不會直接安裝react-router,而是根據(jù)應(yīng)用運行的環(huán)境選擇安裝react-router-dom(在瀏覽器中使用)或react-router-native(在rn中使用)
??react-router-dom和react-router-native都依賴react-router,所以在安裝時,react-router也會自動安裝,創(chuàng)建web應(yīng)用
- 安裝
npm install --save react-router-dom
- 基本使用
react-router中奉行一切皆組件的思想
?? 路由器 ->Router、
?? 鏈接 ->Link、
?? 路由 ->Route、
?? 獨占 ->Switch、
?? 重定向 ->Redirect
?? ...
?? 都是以組件形式存在
- 舉個??
// RouterPage.js
import React, { Component } from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
export default class RouterPage extends Component {
render() {
return (
<div>
<h3>RouterPage</h3>
<Router>
<Link to="/">首頁</Link>
<Link to="/user">用戶中心</Link>
{/* 渲染的三種方式,優(yōu)先級 children > component > render */}
<Route
exact
path="/"
component={HomePage}
children={() => <div>child</div>}
render={() => <div>render</div>}
/>
<Route path="/user" component={UserPage} />
</Router>
</div>
);
}
}
class HomePage extends Component {
render() {
return (
<div>
<h3>HomePage</h3>
</div>
);
}
}
class UserPage extends Component {
render() {
return (
<div>
<h3>UserPage</h3>
</div>
);
}
}
Route渲染內(nèi)容的三種方式:
?? 優(yōu)先級:children > component > render 這三種是互斥的關(guān)系?? children: func
不管location是否匹配,你都需要渲染一些內(nèi)容,這時候可以用children,除此之外和render的工作方法一樣
?? render: func
用render的時候,調(diào)用的只是一個函數(shù),只有在location匹配的時候渲染
?? component: component
只有在location匹配的時候渲染
- 404頁面
為了更好的用戶體驗,設(shè)定一個沒有path的路由在路由列表最后面,表示一定匹配
// RouterPage.js
import React, { Component } from "react";
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
export default class RouterPage extends Component {
render() {
return (
<div>
<h3>RouterPage</h3>
{/* 獨占路由:添加Switch表示僅匹配一個 */}
<Switch>
<Router>
<Link to="/">首頁</Link>
<Link to="/user">用戶中心</Link>
{/* 渲染的三種方式,優(yōu)先級 children > component > render */}
{/* 根路由要添加exact,實現(xiàn)精確匹配 */}
<Route
exact
path="/"
component={HomePage}
children={() => <div>child</div>}
render={() => <div>render</div>}
/>
<Route path="/user" component={UserPage} />
<Route component={EmptyPage} />
</Router>
</Switch>
</div>
);
}
}
class HomePage extends Component {
render() {
return (
<div>
<h3>HomePage</h3>
</div>
);
}
}
class UserPage extends Component {
render() {
return (
<div>
<h3>UserPage</h3>
</div>
);
}
}
class EmptyPage extends Component {
render() {
return (
<div>
<h3>EmptyPage-404</h3>
</div>
);
}
}
PureComponent
PureComponent -> 純組件
- 實現(xiàn)性能優(yōu)化(淺比較)
缺點:必須要用class形式,而且要注意是淺比較即僅作對象的淺層?較,如果對象中包含復(fù)雜的數(shù)據(jù)結(jié)構(gòu),則有可能因為?法檢查深層的差別,產(chǎn)?錯誤的?對結(jié)果。所以僅在你的props 和 state 較為簡單時,才使? React.PureComponent ,或者在深層數(shù)據(jù)結(jié)構(gòu)發(fā)?變化時 調(diào)? forceUpdate() 來確保組件被正確地更新
import React, { Component, PureComponent } from "react";
// PureComponent 內(nèi)置了shouldComponentUpdate的比較
export default class PureComponentPage extends PureComponent {
constructor(props) {
super(props);
this.state = {
// 可實現(xiàn)性能優(yōu)化
count: 0,
// 實現(xiàn)不了,PureComponent只能實現(xiàn)淺比較
obj: {
num: 1,
},
};
}
setCount = () => {
this.setState({
count: 1000,
obj: {
num: 1000,
},
});
};
// shouldComponentUpdate(nextProps, nextState) {
// // 性能優(yōu)化,只有值變化了才會更新
// return nextState.count !== this.state.count;
// }
render() {
const { count } = this.state;
console.log("render");
return (
<div>
<h3>PureComponentPage</h3>
<button onClick={this.setCount}>{count}</button>
</div>
);
}
}
- PureComponent于Component的比較
PureComponent和Component很相似。區(qū)別在于React.Component并未實現(xiàn)shouldComponentUpdate,而PureComponent中以淺層對比prop和state的方式實現(xiàn)了該函數(shù)。
