資源 1. react 2. create-r eact-app 起步 1. 安裝官方腳手架: npm install -g create-react-app 2. 創建項目: create-react-app react-study 3. 啟動項目: npm start 文件結構 文件結構一覽 cr a項目真容 使用 npm run eject彈出項目真面目,會多出兩個目錄: ├── README.md 文檔 ├── public 靜態資源 │ ├── 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 依賴95267ee38bfe5802
env .js用來處理 .env 文件中配置的環境變量 實踐一下,修改一下默認埠號,創建 .env 文件 webpack.cong.js 是webpack 配置文件,開頭的常量聲明可以看出 cra能夠支持 ts、sass 及css 模塊化 ├── config ├── env.js 處理.env環境變量配置文件 ├── paths.js 提供各種路徑 ├── webpack.config.js webpack配置文件 └── webpackDevServer.config.js 測試伺服器配置文件 └── scripts 啟動、打包和測試腳本 ├── build.js 打包腳本、 ├── start.js 啟動腳本 └── test.js 測試腳本 // node運行環境:development、production、test等 const NODE_ENV = process.env.NODE_ENV; // 要掃描的文件名數組 var dotenvFiles = [ `${paths.dotenv}.${NODE_ENV}.local`, // .env.development.local `${paths.dotenv}.${NODE_ENV}`, // .env.development NODE_ENV !== 'test' && `${paths.dotenv}.local`, // .env.local paths.dotenv, // .env ].filter(Boolean); // 從.env*文件加載環境變量 dotenvFiles.forEach(dotenvFile => { if (fs.existsSync(dotenvFile)) { require('dotenv-expand')( require('dotenv').config({ path: dotenvFile, }) ); } }); PORT=8080 // Check if TypeScript is setup const useTypeScript = fs.existsSync(paths.appTsConfig); // style files regexes const cssRegex = /\.css$/; const cssModuleRegex = /\.module\.css$/; const sassRegex = /\.(scss|sass)$/; const sassModuleRegex = /\.module\.(scss|sass)$/;95267ee38bfe5802
React 和 ReactDom 刪除 src下面所有代碼,新建 inde x.js React 負責邏輯控制,數據 -> VDOM ReactDom 渲染實際 DOM ,VDOM -> DOM ,如果換到移動端,就用別的庫來渲染 React 使用 JSX 來描述 UI 入口文件定義, webpack.cong.js JSX JSX 是一種 JavaScript 的語法擴展,其格式比較像模版語言,但事實上完全是在 JavaScript 內部實現的。 JSX 可以很好地描述 UI,能夠有效提高開發效率 使用 JSX 表達式 {}的使用, inde x.js 函數也是合法表達式, inde x.js import React from 'react'; import ReactDOM from 'react-dom'; ReactDOM.render(
React真帥
, document.querySelector('#root')); entry: [ // WebpackDevServer客戶端,它實現開發時熱更新功能 isEnvDevelopment && require.resolve('react-dev-utils/webpackHotDevClient'), // 應用程式入口:src/index paths.appIndexJs, ].filter(Boolean), const name = "react study"; const jsx =
{name}
; const user = { firstName: "tom", lastName: "jerry" }; function formatName(user) { return user.firstName + " " + user.lastName; }const jsx =
{formatName(user)}
jsx 是js對象,也是合法表達式, inde x.js 條件語句可以基於上面結論實現, inde x.js 數組會被作為一組子元素對待,數組中存放一組 jsx 可用於顯示列表數據 屬性的使用 css 模塊化,創建 inde x.module.css ,inde x.js const greet =
hello, Jerry
const jsx =
{greet}
; const showTitle = true; const title = name ?
{name}
: null; const jsx = (
{/* 條件語句 */} {title}
);const arr = [1,2,3].map(num =>
{num})const jsx = ( {/* 數組 */} {arr});import logo from "./logo.svg";const jsx = ({/*屬性:靜態值用雙引號,動態值用花括號;class、for等要特殊處理。*/});import style from "./index.module.css";95267ee38bfe5802組件 組件的兩種形式 class 組件 class 組件通常 擁有狀態 和生命周期 ,繼承於 Component ,實現 render 方法 components/JsxT est.js function 組件 函數組件通常 無狀態 ,僅 關注內容展示 ,返回渲染結果即可。 App.js import React, { Component } from "react"; import logo from "../logo.svg"; import style from "../index.module.css"; export default class JsxTest extends Component { render() { const name = "react study"; const user = { firstName: "tom", lastName: "jerry" }; function formatName(user) { return user.firstName + " " + user.lastName; } const greet =
hello, Jerry
; const arr = [1, 2, 3].map(num =>
{num}); return ({/*條件語句*/}{name?{name}:null}{/*函數也是表達式*/}{formatName(user)}{/*jsx也是表達式*/}{greet}{/*數組*/}{arr}{/*屬性*/} ); }}95267ee38bfe5802組件狀態管理 類組件中的狀態管理 components/StateMgt.js創建一個 Clock 組件 import React from "react"; import JsxTest from "./components/JsxTest"; function App() { return (
); }export default App; import React, { Component } from "react"; export default function StateMgt { return (
); }class Clock extends React.Component { constructor(props) { super(props); // 使用state屬性維護狀態,在構造函數中初始化狀態 this.state = { date: new Date() }; } componentDidMount() { // 組件掛載時啟動定時器每秒更新狀態 this.timerID = setInterval(() => { // 使用setState方法更新狀態 this.setState({ date: new Date() }); }, 1000); } componentWillUnmount() { // 組件卸載時停止定時器95267ee38bfe5802
拓展: setState 特性討論 用setState 更新狀態而不能直接修改 setState 是批量執行的,因此對同一個狀態執行多次只起一次作用,多個狀態更新可以放在同一個 setState 中進行: setState 通常是異步的,因此如果要獲取到最新狀態值有以下三種方式: 1. 傳遞函數給 setState 方法, 2. 使用定時器: 3. 原生事件中修改狀態 函數組件中的狀態管理 clearInterval(this.timerID); } render() { return
{this.state.date.toLocaleTimeString()}
; } } this.state.counter += 1; //錯誤的 componentDidMount() { // 假如couter初始值為0,執行三次以後其結果是多少? this.setState({counter: this.state.counter + 1}); this.setState({counter: this.state.counter + 1}); this.setState({counter: this.state.counter + 1}); } this.setState((state, props) => ({ counter: state.counter + 1}));// 1 this.setState((state, props) => ({ counter: state.counter + 1}));// 2 this.setState((state, props) => ({ counter: state.counter + 1}));// 3 setTimeout(() => { console.log(this.state.counter); }, 0); componentDidMount(){ document.body.addEventListener('click', this.changeValue, false) }changeValue = () => { this.setState({counter: this.state.counter+1}) console.log(this.state.counter) }95267ee38bfe5802
事件處理 React 中使用 onXX 寫法來監聽事件。 範例:用戶輸入事件,創建 EventHandle.js import { useState, useEffect } from "react"; function ClockFunc() { // useState創建一個狀態和修改該狀態的函數 const [date, setDate] = useState(new Date()); // useEffect編寫副作用代碼 useEffect(() => { // 啟動定時器是我們的副作用代碼 const timerID = setInterval(() => { setDate(new Date()); }, 1000); // 返回清理函數 return () => clearInterval(timerID); }, []);// 參數2傳遞空數組使我們參數1函數僅執行一次 return
{date.toLocaleTimeString()}
; }import React, { Component } from "react"; export default class EventHandle extends Component { constructor(props) { super(props); this.state = { name: "" }; this.handleChange = this.handleChange.bind(this); } handleChange(e) { this.setState({ name: e.target.value }); } render() { return (
{/* 使用箭頭函數,不需要指定回調函數this,且便於傳遞參數 */} {/*<input type="text" value={this.state.name} onChange={e => this.handleChange(e)} /> */} {/* 直接指定回調函數,需要指定其this指向,或者將回調設置為箭頭函數屬性 */} <input95267ee38bfe5802
組件通信 Pr ops 屬性傳遞 Props 屬性傳遞可用於父子組件相互通信 如果父組件傳遞的是函數,則可以把子組件信息傳入父組件,這個常稱為狀態提升, StateMgt.js conte xt 跨層級組件之間通信redux 類似 vue x,無明顯關係的組件間通信 type="text" value={this.state.name} onChange={this.handleChange} />
{this.state.name}
); } }// index.js ReactDOM.render(
生命周期 React V16.3 之前的生命周期 image-20190111173300397 範例:驗證生命周期,創建 Lifecycle.js import React, { Component } from "react"; export default class Lifecycle extends Component { constructor(props) { super(props); // 常用於初始化狀態 console.log("1.組件構造函數執行"); } componentWillMount() { // 此時可以訪問狀態和屬性,可進行api調用等 console.log("2.組件將要掛載"); } componentDidMount() { // 組件已掛載,可進行狀態更新操作 console.log("3.組件已掛載"); } componentWillReceiveProps(nextProps, nextState) { // 父組件傳遞的屬性有變化,做相應響應 console.log("4.將要接收屬性傳遞"); } shouldComponentUpdate(nextProps, nextState) { // 組件是否需要更新,需要返回布爾值結果,優化點 console.log("5.組件是否需要更新?"); return true; } componentWillUpdate() { // 組件將要更新,可做更新統計 console.log("6.組件將要更新"); } componentDidUpdate() { // 組件更新 console.log("7.組件已更新"); } componentWillUnmount() { // 組件將要卸載, 可做清理工作 console.log("8.組件將要卸載"); } render() { console.log("組件渲染"); return
生命周期探究
;95267ee38bfe5802
激活更新階段: App.js 激活卸載階段, App.js 生命周期探究 React v16.0 前的生命周期 大部分團隊不見得會跟進升到 16 版本,所以 16 前的生命周期還是很有必要掌握的,何況 16 也是基於之前的修改 } }class App extends Component { state = { prop: "some content" }; componentDidMount() { this.setState({ prop: "new content" }); } render() { return (
); } }class App extends Component { state = { prop: "some content" }; componentDidMount() { this.setState({ prop: "new content" }); setTimeout(() => { this.setState({ prop: "" }); }, 2000); } render() { return (
{this.state.prop &&
); } }95267ee38bfe5802
第一個是組件初始化 (initialization) 階段 也就是以下代碼中類的構造方法 ( constructor() ), Test 類繼承了 react Component 這個基類,也就繼承這個 react 的 基類,才能有 render(), 生命周期等方法可以使用,這也說明為什麼 函數組件不能使用這些方法 的原因。 super(props)用來調用基類的構造方法 ( constructor() ), 也將父組件的 props 注入給子組件,功子組件讀取 (組件 中props 只讀不可變, state 可變 )。 而 constructor()用來做一些組件的初始化工作,如定義 this.state 的初始內 容。import React, { Component } from 'react'; class Test extends Component { constructor(props) { super(props); } } 第二個是組件的掛載 (Mounting) 階段 此階段分為 componentWillMount ,render ,componentDidMount 三個時期。 componentWillMount: 在組件掛載到 DOM 前調用,且只會被調用一次,在這邊調用 this.setState 不會引起組件重新渲染,也可以把寫在這 邊的內容提前到 constructor() 中,所以項目中很少用。 render: 根據組件的 props 和state (無兩者的重傳遞和重賦值,論值是否有變化,都可以引起組件重新 render ) ,return 一個 React 元素(描述組件,即 UI),不負責組件實際渲染工作,之後由 React 自身根據此元素去渲染出頁面 DOM 。render 是純函數( Pur e function :函數的返回結果只依賴於它的參數;函數執行過程裡面沒有副作用), 不能在裡面執行 this.setState ,會有改變組件狀態的副作用。 componentDidMount:95267ee38bfe5802
組件掛載到 DOM 後調用,且只會被調用一次 第三個是組件的更新 (update) 階段 在講述此階段前需要先明確下 react 組件更新機制。 setState 引起的 state 更新或父組件重新 render 引起的 props 更 新,更新後的 state 和props 相對之前無論是否有變化,都將引起子組件的重新 render 。詳細可看 這篇文章 造成組件更新有兩類(三種)情況: 1.父組件重新 render 父組件重新 render 引起子組件重新 render 的情況有兩種 a. 直接使用 ,每當父組件重新 render 導致的重傳 props ,子組件將直接跟著重新渲染,無論 props 是否有變化。可通 過shouldComponentUpdate 方法優化。 class Child extends Component { shouldComponentUpdate(nextProps){ // 應該使用這個方法,否則無論props是否有變化都將會導致組件跟 著重新渲染 if(nextProps.someThings === this.props.someThings){ return false } } render() { return
{this.props.someThings}
} } b.在componentWillReceivePr ops 方法中,將 props 轉換成自己的 state class Child extends Component { constructor(props) { super(props); this.state = { someThings: props.someThings }; } componentWillReceiveProps(nextProps) { // 父組件重傳props時就會調用這個方法 this.setState({someThings: nextProps.someThings}); } render() { return
{this.state.someThings}
} } 根據官網的描述 在該函數 (componentWillReceivePr ops) 中調用 this.setState() 將不會引起第二次渲染。 是因為 componentWillReceivePr ops 中判斷 props 是否變化了,若變化了, this.setState 將引起 state 變化,從而引 起render ,此時就沒必要再做第二次因重傳 props 引起的 render 了,不然重複做一樣的渲染了。 2.組件本身調用 setState ,無論 state 有沒有變化。可通過 shouldComponentUpdate 方法優化。95267ee38bfe5802
class Child extends Component { constructor(props) { super(props); this.state = { someThings:1 } } shouldComponentUpdate(nextStates){ // 應該使用這個方法,否則無論state是否有變化都將會導致組件 重新渲染 if(nextStates.someThings === this.state.someThings){ return false } } handleClick = () => { // 雖然調用了setState ,但state並無變化 const preSomeThings = this.state.someThings this.setState({ someThings: preSomeThings }) } render() { return
{this.state.someThings}
} } 此階段分為 componentWillReceivePr ops , shouldComponentUpdate , componentWillUpdate , render , componentDidUpdate componentWillReceivePr ops(ne xtPr ops) 此方法只調用於 props 引起的組件更新過程中,參數 ne xtPr ops 是父組件傳給當前組件的新 props 。但父組件 render 方法的調用不能保證重傳給當前組件的 props 是有變化的,所以在此方法中根據 ne xtPr ops 和this.pr ops 來查明重傳 的props 是否改變,以及如果改變了要執行啥,比如根據新的 props 調用 this.setState 出發當前組件的重新 render shouldComponentUpdate(ne xtPr ops, ne xtState) 此方法通過比較 ne xtPr ops ,ne xtState 及當前組件的 this.pr ops ,this.state ,返回 true 時當前組件將繼續執行更新 過程,返回 false 則當前組件更新停止,以此可用來減少組件的不必要渲染,優化組件性能。 ps :這邊也可以看出,就算 componentWillReceivePr ops() 中執行了 this.setState ,更新了 state ,但在 render 前 (如 shouldComponentUpdate ,componentWillUpdate ), this.state 依然指向更新前的 state ,不然 ne xtState 及當前組件的 this.state 的對比就一直是 true 了。 componentWillUpdate(ne xtPr ops, ne xtState) 此方法在調用 render 方法前執行,在這邊可執行一些組件更新發生前的工作,一般較少用。 render render 方法在上文講過,這邊只是重新調用。componentDidUpdate(pr evPr ops, pr evState)95267ee38bfe5802
此方法在組件更新後被調用,可以操作組件更新的 DOM ,prevPr ops 和prevState 這兩個參數指的是組件更新前的 props 和state 卸載階段 此階段只有一個生命周期方法: componentWillUnmount componentWillUnmount 此方法在組件被卸載前調用,可以在這裡執行一些清理工作,比如清楚組件中使用的定時器,清楚componentDidMount 中手動創建的 DOM 元素等,以避免引起內存洩漏。 React v16.4 的生命周期 變更緣由 原來( React v16.0 前)的生命周期在 React v16 推出的 Fiber 之後就不合適了,因為如果要開啟 async r endering , 在render 函數之前的所有函數,都有可能被執行多次。 原來( React v16.0 前)的生命周期有哪些是在 render 前執行的呢? componentWillMountcomponentWillReceivePr ops shouldComponentUpdatecomponentWillUpdate95267ee38bfe5802
如果開發者開了 async r endering ,而且又在以上這些 render 前執行的生命周期方法做 AJAX 請求的話,那 AJAX 將被 無謂地多次調用。。。明顯不是我們期望的結果。而且在 componentWillMount 裡發起 AJAX ,不管多快得到結果 也趕不上首次 render ,而且 componentWillMount 在伺服器端渲染也會被調用到(當然,也許這是預期的結 果),這樣的 IO 操作放在 componentDidMount 裡更合適。 禁止不能用比勸導開發者不要這樣用的效果更好,所以除了 shouldComponentUpdate ,其他在 render 函數之前的 所有函數( componentWillMount ,componentWillReceivePr ops ,componentWillUpdate )都被 getDerivedStateFr omPr ops 替代。 也就是用一個靜態函數 getDerivedStateFr omPr ops 來取代被 depr ecate 的幾個生命周期函數,就是強制開發者在 render 之前只做無副作用的操作,而且能做的操作局限在根據 props 和state 決定新的 state React v16.0 剛推出的時候,是增加了一個 componentDidCatch 生命周期函數,這只是一個增量式修改,完全不影 響原有生命周期函數;但是,到了 React v16.3 ,大改動來了,引入了兩個新的生命周期函數。 新引入了兩個新的生命周期函數: getDerivedStateFromProps , getSnapshotBeforeUpdate getDerivedStateFr omPr ops static getDerivedStateFr omPr ops(pr ops, state) 在組件創建時和更新時的 render 方法之前調用,它應該返回 一個對象來更新狀態,或者返回 null 來不更新任何內容。 getDerivedStateFromProps本來( React v16.3 中)是只在創建和更新(由父組件引發部分),也就是不是由父 組件引發,那麼 getDerivedStateFr omPr ops 也不會被調用,如自身 setState 引發或者 for ceUpdate 引發。 React v16
這樣的話理解起來有點亂,在 React v16.4 中改正了這一點,讓 getDerivedStateFr omPr ops 無論是 Mounting 還是 Updating ,也無論是因為什麼引起的 Updating ,全部都會被調用,具體可看 React v16.4 的生命周期圖。 getSnapshotBefor eUpdate getSnapshotBefor eUpdate() 被調用於 render 之後,可以讀取但無法使用 DOM 的時候。它使您的組件可以在可 能更改之前從 DOM 捕獲一些信息(例如滾動位置)。此生命周期返回的任何值都將作為參數傳遞給 componentDidUpdate ()。 官網給的例子:class ScrollingList extends React.Component { constructor(props) { super(props); this.listRef = React.createRef(); } getSnapshotBeforeUpdate(prevProps, prevState) { //我們是否要添加新的 items 到列表? // 捕捉滾動位置,以便我們可以稍後調整滾動. if (prevProps.list.length < this.props.list.length) { 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; } } render() { return (
{/* ...contents... */}
); }