webstrom自動格式化代碼
命令
js框架 MVC
安裝
npm install create-react-app -g
生成項目(項目名npm發包包命名規范 /^[a-z0-9_-]$/)
create-react-app 項目名字
查看全局安裝目錄
npm root -g
文件
public 存放的是當前項目的HTML頁面(單頁面應用放index.html即可)
html 導入的地址應該寫成絕對路徑 %PUBLIC_URL% public的文件夾
不能用相對路徑
src 項目結構最主要的目錄,后期的js,路由組件都放這里
index.js 是當前目錄的入口文件
react-scripts 是webpack的所有配置
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
}
執行命令 npm run start /yarn start
React腳手架的深入剖析
暴露webpack配置項
為了結構的美化,把所有的webpack配置等都隱藏到了node_modules中(react-script)
yarn eject
首先會提示確認是否執行eject操作,操作是不可逆轉的,一旦暴露出來配置項,就無法再隱藏回去了
報錯信息
Remove untracked files, stash or commit any changes, and try again.
如果當前的項目基於git管理,在執行eject的時候,如果還沒有提交到歷史區內容,需要先提交歷史區,然后再eject才可以,否則報錯
* 需要在vcs 里面commit 進行git操作
再進行 yarn eject
之后多個兩個文件夾
config 存放的是webpack的配置文件
webpack.config.dev.js 開發環境下的配置項(yarn start)
webpack.config.prod.js 生產環境下的配置項(yarn build)
scripts 存放的是可執行腳本的js文件
start.js yarn start執行的就是這個js
build.js yarn build 執行的就是這個就是
配置less
yarn add less less-loader
less 開發和生產都需要改
參考
https://www.cnblogs.com/esofar/p/9631657.html
https://www.cnblogs.com/jayxiangnan/p/9116663.html
set HTTPS=true&&npm start 開啟HTTPS協議模式
set PORT=63341 修改端口號
配置sass
yarn add node-sass sass-loader -D
如果yarn不能安裝就用cnpm
less類似一樣的
{
test:/\.scss$/,
loaders:['style-loader','css-loader','sass-loader'],
},

npm install less less-loader --save-dev
去掉webstorm 報灰色線
Editor -> Colors & Fonts -> General , 在Errors and Warnings里把Wadk Warning里的 兩項勾選去掉。

撤銷工作區的修改
已修改,未暫存(撤銷工作區的修改)
git reset --hard
react & react-dom
漸進式框架
我們應該把框架的功能進行拆分,用戶想用什么,讓其自己自由組合即可
全家桶
漸進式框架N多部分的組合
VUE全局桶(vue-cli/vue/vue-router/vuex/axios(fetch)/vue element(vant))
REACT全家桶: create-react-app/react/react-dom/react-router/redux/react-redux/axios/ant/sage/mobx
react:REACT框架的核心部分,提供了Component類可以共我們進行組件開發,提供了鈎子函數(生命周期函數)
所有的生命周期函數都是基於回調函數完成的
react-dom :把JSX語法(REACT特有的語法)渲染為真實DOM(能夠展示的結構都叫真實的DOM)
* 不推薦jsx的容器是body
* 只能出現一個根元素
* 給元素設置樣式類用的是className而不是class
* style={{}}
* jsx元素設置屬性 ,屬性值對應大括號中 對象,函數都可以放(也可以放js表達式)
將數據嵌入jsx中,可以嵌入變量或者直接的數據值
let name='xxx';
ReactDOM.render(<div>
<h1>{name}</h1>
<h2>{'haha'}</h2>
</div>)
不能嵌入對象(代指: {} /^$/ 日期對象 函數 數據中的某一項是前面也不行)
可以嵌入基本類型值(null/undefined/布爾值都是空元素,也就是不顯示任何內容)
把JSX(虛擬DOM) 變為真實的DOM(不太理解)--(8,9)
循環創建jsx元素需要設置標識key,並且在當前循環的時候,這個key需要唯一
let name='珠峰培訓',
data=[{id:1,title:'xxx'},{id:2, title: 'xxx'}];
ReactDOM.render(<ul style={{color:'red'}} className={'box clearfix'} OnClick={(ev)=>{
console.log(ev);
}}>
{data.map((item,index)=>{
return <li key={index}>
{item.id}
{item.title}
</li>
})}
</ul>,root);
使用三元運算符解決判斷操作(if和swich是不可以的)
React是如何把jsx元素轉換為真實的DOM元素並且添加到頁面中
基於babel/babel-loader: 把jsx語法編譯為react.create-Element這種模式
create-Element 至少有兩個參數,沒有上限
第一個: 標簽名(字符串)
第二個:屬性(沒有給元素設置null)
其他:當前元素的所有子元素內容
執行create-Element 把傳遞的參數最后處理成為一個對象
React 組件
組件/模塊管理 ,就是把一個程序划分為一個個組件來單獨處理
優勢
- 有助於多人協助開發
- 提高復用性
React創建組件有兩種方式
- 函數聲明式組件
- 基於繼承component類來創建組件
src->component 這個文件夾存放開發的組件
create-element 在處理的時候,遇到一個組件,返回的對象中type就不再是字符串,而是一個函數(類),但是屬性還是props中
({
type:Dialog,
props:{
lx:1,
con:'xxx',
children:一個值或者一個數組
}
}
首先判斷type是什么類型,如果是字符串就創建一個元素標簽,如果函數或者類,就把函數執行,把props中的每一項(包含children傳遞給函數)
React.Children.map
基於繼承component類創建組件
基於create-element 把jax 轉換為一個對象,當react渲染這個對象的時候,遇到type是一個函數或者類,不是直接創建元素,而是先把方法執行:
* 如果是函數式聲明的組件,就把它當作普通方法執行(方法中的this是undefiend),把函數返回的jsx元素進行渲染
* 如果是類聲明式的組件,會把房錢類new它執行,創建類的一個實例(當前本次調用組件就是它的實例)執行constructor之后,會執行this.render(),把render中返回的jsx拿過來渲染,所以 類聲明式組件,必須有一個render的方法,方法中需要一個jsx元素
但是不管是哪一種方式,最后都會拿解析出來的props屬性對象作為實參給對應的函數或者類
創建組件有兩種方式"函數式","創建類式"
函數式
- 簡單
- 能實現的功能也很簡單,只是簡單的調取和返回jsx而已
ReactDOM.render(<div> /*單閉合*/ <Vote title={xxx}/> /*雙閉合*/ <Vote> <p>11111111</p> </Vote> </div>) import React from 'react'; exprot default function Vote(props){ return <div className={'penel panel-default'}> {props.Children} {props.Children.map(props,children,item=>{return item;})} </div> }創建類式
操作相對復雜一些,但是也可以實現更為復雜的業務功能
能夠使用生命周期函數操作業務
函數式可以理解為靜態組件(組件中的內容調取的時候就已經固定了,很難在修改,而這種類的組件可以基於組件內部的狀態來動態更新渲染的內容
import React from 'react'; //ref是react操作DOM的方案 //* 給需要操作的元素設置ref(保持唯一性,否則會沖突覆蓋) //* 在實例上掛載了refs屬性,他是一個對象,存儲了所有設置ref的元素(ref值:元素對象) export default class Vote extends React.Component{ constructor(props){ super(props); //React.Component.call(this)可以把component中的私有實例繼承過來,this.props/this.state(this.setState)/this.content/this.refs/this.updater //初始化狀態 this.state={ n:0, m:0, } } //狀態處理方式 render(){ let {title,children}=this.props, {n,m}=this.state; return <div> 支持:<span>{m}</span> 反對: <span>{n}</span> </div> } //DOM處理方式 render(){ let {title,children}=this.props, {n,m}=this.state; return <div> 支持:<span ref={'AA'}>0</span> 反對: <span ref={'BB'}>0</span> 平局值: <span ref={'CC'}>0</span> </div> } suport=ev=>{ this.refs.AA.innerHTML++; this.computed(); } suport=ev=>{ this.refs.BB.innerHTML++; this.computed(); } computed=()=>{ let {AA,BB}=this.refs; //然后再進行操作 } supprot=ev=>{ this.refs.AA.innerHTML++; //使用箭頭函數式為了保證方法中this永遠是實例本身(無論在哪執行這個方法) //ev.target獲取當前操作的事件源(dom元素) this.setState({ //修改狀態信息並且通知render重新渲染(異步操作:如果有其他代碼執行,先執行其他代碼,然后再通知狀態修改) n:this.state.n+1 },()=>{ //回調函數一般不用,當通知狀態修改完成,並且頁面重新渲染完成后,執行回調 }) } }
yarn add prop-types
基於這個插件我們可以給組件傳遞的屬性設置規則
設置的規則不會影響頁面的,但是會控制台報錯

顯示當前文件的最新版本信
返回上一個版本
生命周期函數(鈎子函數)
-
描述一個組件或者程序從創建到銷毀的過程,我們可以再過程中基於鈎子函數完成一些自己的操作
-

基本流程 constructor 創建一個組件 componentWillMout 第一次渲染之前 render 第一次渲染 componentDidMout 第一次渲染之后 修改流程:當組件的狀態數據發生改變(setState)或者傳遞給組件的屬性發生改變 shouldComponentUpdate 是否允許組件重新渲染(允許則執行后面函數,不允許直接結束即可) componentWillUpdate 重新熏染之前 render 第二次以后重新渲染 componentDidUpdate 重新渲染之后 屬性改變: componentWillReceiveProps(nextProps/nextState):父組件把傳遞給組組建的屬性發生改變后出發的鈎子函數 接受最新屬性之前,基於this.props.xxx 獲取的是原有的屬性信息,nextProps存儲的是最新傳遞的屬性信息 shouldComponentUpdate 是否允許組件更新, 返回true是允許,返回false 是不在繼續走 componentWillUpdate: 更新之前: 和should一樣,方法中通過this.state.xxx 獲取的還是更新前的狀態信息,方法有兩個參數:nextProps/nextState存儲的是最新的屬性和狀態 render 更新 componentDidUpdate 更新之后 卸載 componentWillUnmount :卸載組件之前(一般不用)
React是face-Book 公司開發的一款MVC版js 框架
MVC Model (數據層) View(視圖層) controller(控制層)
核心思想: 通過數據的改變來影響視圖的渲染(數據)
屬性的屬性是只讀的:只能調用組件時候傳遞進來,不能自己在組建內部修改(但是可以設置默認值和規則)
復合組件
- 復合組件:父組件嵌套子組件
傳遞信息的方式
父組件需要把信息傳遞給子組件
屬性傳遞:調取子組件的時候,把信息基於屬性的方式傳遞給子組件(子組件props中存儲傳遞的信息),這種方式只能父組件把信息傳遞給子組件,子組件無法直接的把信息傳遞給父組件,也就是屬性傳遞信息是單向傳遞的;
export default class Vote extends React.Component { static defaultProps = { title: '標題不知道,隨便投', }; constructor(props) { super(props); } render() { let {title} = this.props; return <VoteHead title={title}/>雙下文傳遞:父組件先把需要給后代元素(包括孫子元素)使用的信息都設置好(設置在上下文中),后代組件需要用到父組件中的信息,主動去父組件中調用使用即可
/* 在父組件中 設置子組件上下文屬性類型 static childContextTypes={} 獲取子組件的上下文(設置子組件的上下文屬性信息) getChildContext(){} */ static childContextTypes = { //設置上下文信息值的類型 n: PropTypes.number, m: PropTypes.number, }; getChildContext() { //return 是啥就是想當於給子組件設置下上文 let {count: {n = 0, m = 0}} = this.props; return { n, m, } } /* * 子組件設置使用傳遞進來的上下文類型 * 設置那個的類型,子組件上下文中才有那個屬性,不設置是不允許使用的 * this.context.xxx * 指定的上下文屬性類型需要和父組件指定的類型保持一致,否則報錯 * */ static contextTypes={ n:PropTypes.number, m:PropTypes.number }; constructor(props,context){ super(props,context); this.context.n=1000 }屬性VS 上下文
屬性操作起來簡單,子組件是被動接受傳遞的值(組件內的屬性是只讀的),只能父傳子(子傳父不行,父傳孫也需要處理:子傳子,子再傳孫)
上下文操作起來相對復雜一些,子組件是主動獲取信息使用的(子組件是可以修改獲取的上下文信息,但是不會影響到父組件中的信息,其他組件不受影響),一旦父組件設置了上下文信息,他后代組件都可以直接拿到,不需要不層層的傳遞
其實子組件也能修改父組件中的信息
利用回調函數機制: 父組件把一個函數通過屬性或者上下文的方式傳遞給子組件,子組件要把這個方法執行即可
(也就是子組件中執行了父組件方法,還可以傳遞一些值過去),這樣組件在這個方法中,想把自己的信息改成啥就改成啥
/* 在父組件中 設置子組件上下文屬性類型 static childContextTypes={} 獲取子組件的上下文(設置子組件的上下文屬性信息) getChildContext(){} */ static childContextTypes = { n: PropTypes.number, m: PropTypes.number, callBack: PropTypes.func }; getChildContext() { //return 是啥就是想當於給子組件設置下上文 //只要render重新渲染,就會執行這個方法,重新更新父組件中的上下文信息,如果父組件上下文 //信息更改了,子組件在重新調取的時候,會使用最新的上下文信息(render=>context=>子組件調取渲染) let {n, m} = this.state; return { n, m, callBack: this.updateContext } } updateContext = type => { //type :'support'/'against' if (type === 'support') { this.setState({n: this.state.n + 1}); return; } return this.setState({m:this.state.m-1}) }; //子組件 <button className={'btn btn-danger'} onClick={ ()=>{ callBack('against'); } }>反對</button>
- 平行組件:兄弟組件或者毫無關系的兩個組件
- 讓兩個平行組件擁有一個共同的父組件
- 基於redux來進行狀態管理,實現組件之間的信息傳遞
- redux可以應用在任何項目中(vue/jq/react的都可以),react-redux才是專門給react項目提供的方案
yarn add redux react-redux//index.js //創建一個容器: 需要把reducer 傳遞進來(登記了所有狀態更改的信息) import {createStore} from 'redux'; /*reducer 作用: 1. 記錄了所有狀態修改的信息(根據行為標識走不同的修改任務) 2. 修改容器中的狀態信息 [參數] state:容器中原有的狀態信息(如果第一次使用,沒有原有狀態,給一個廚師默認值 action: dispatch 任務派發的時候傳遞的行為對象(這個對象中必有一個type屬性,是操作的行為標識, reducer 就是根據這個行為標識來識別修改狀態信息 * */ let reducer = (state = {n: 0, m: 0}, action) => { switch (action.type) { case 'VOTE_SUPPORT': //vote_support state = {...state, n: state.n + 1}; break; case 'VOTE_AGAINST': //vote_against state = {...state, m: state.m + 1}; break; } return state;// 只有把最新的state返回,原有的狀態才會修改 }; let store = createStore(reducer); /* * 創建store 提供三個方法: * dispatch: 派發行為(傳遞一個對象,對象中有一個type屬性,通知reducer修改狀態信息 * subscribe: 事件池追加方法 * getState: 獲取最新管理的狀態信息 * */ <Vote title={'英格蘭對戰巴拿馬,合力必勝'} count={{ n: 100, m: 78 }} store={store}/>//Vote.js import React from 'react'; import PropTypes from 'prop-types'; import VoteHead from './VoteHead'; import VoteBody from './VoteBody'; import VoteFooter from "./VoteFooter"; export default class Vote extends React.Component { static defaultProps = { title: '', count: { n: 0, m: 0, } }; constructor(props) { super(props); }; render() { let {store} = this.props; return <section className={'panel-heading'} style={{width: '50%', height: '20px auto'}}> <VoteHead title={this.props.title}/> <VoteBody store={store}/> <VoteFooter store={store}/> </section> } }//VoteBody.js import React from 'react'; import PropTypes from 'prop-types'; export default class VoteBody extends React.Component { constructor(props) { super(props); //init state let {store: {getState}} = this.props, {n, m} = getState(); this.state = {n, m}; } componentDidMount() { let {store: {getState, subscribe}} = this.props; let unsubscribe = subscribe(() => { let {n, m} = getState(); this.setState({ n, m }) }); //unsubscribe(): 當前追加的方法移出,接觸綁定的方式 } render() { let {n, m} = this.state, rate = (n / (n + m)) * 100; if (isNaN(rate)) { rate = 0; } return <div className={'panel-body'}> 支持人數: <span>{n}</span> <br/> 反對人數: <span>{m}</span> <br/> 支持比率: <span>{rate.toFixed(2) + '%'}</span> </div>; } }//VoteFooter.js import React from 'react'; import PropTypes from 'prop-types'; export default class VoteFooter extends React.Component { constructor(props, context) { super(props, context); } render() { let {store: {dispatch}} = this.props; return <div className={'panel-footer'}> <button className={'btn btn-success'} onClick={() => { dispatch({ type: 'VOTE_SUPPORT' }) }} >支持 </button> <button className={'btn btn-danger'} onClick={ () => { dispatch({ type: 'VOTE_AGAINST' }) } }>反對 </button> </div> } }
Redux
redux:進行狀態統一管理的類庫(適用於任何技術體系的項目)
- 只要兩個或者多個組件之間想要實現信息的共享,都可以基於redux解決,把共享的信息到redux容器中進行管理
- 還可以使用redux做臨時存儲:頁面加載的時候,把從服務器獲取的數據信息存儲到redux中,組件渲染需要的數據,redux中,這樣只要頁面不刷新,路由切換的時候,再次渲染組件不需要重新從服務器拉取數據,直接從redux中獲取即可:頁面刷新,從頭開始(這套方案代替了localStorage本地存儲來實現數據緩存)
Redex管理文件夾
store REDUX管理文件夾
* action 派發行為集合
* vote.js
* ...
* index.js 所有分模塊行為的匯總
*
* reducer 管理員集合
* vote.js
* ...
* index.js 所有管理員的集合匯總
*
* action-types.js 所有行為標識
* index.js 創建REDUX容器


Redex工程化案例
store/index.js
/* * store * reducer: 存放每一個模塊 reducer * vote.js * personal.js * ... * index.js 把每一個模塊reducer最后合並成reducer * action 存放每一個模塊需要進行的派發任務(ActionCreator) * * */ import {createStore} from 'redux'; import reducer from './reducer'; let store = createStore(reducer); export default store;store/action-types.js
/* * 管控當前項目中所有redux任務派發中需要的行為標識action-type * */ //vote_support export const vote_support = 'vote_support'; export const vote_against = 'vote_against'; //personal export const personal_init = 'personal_init';store/action/
index.js ====== /* * 合並所有的action-creator,類似於reducer合並,為了防止沖突,合並后的對象是以版塊名稱單獨划分管理 * */ import vote from './vote'; import personal from './personal'; let action ={ vote, personal }; export default action; personal.js ====== import * as TYPE from '../action-types'; let personal = {}; export default personal; vote.js ====== /* * 每個版塊單獨的action-creator :就是把dispatch派發時候需要傳遞的action對象進一步統一封裝處理 * react-redux中會體驗到他的好處 * */ import * as TYPE from '../action-types'; let vote={ support(){ //dispatch 派發的時候需要傳遞啥就返回啥即可 return { type: TYPE.vote_support }; }, against(){ return { type: TYPE.vote_against } } }; export default vote;store/reducer
index.js ===== /* * 把每一個模塊但是設置的reducer函數最后合並成為總的reducer * 為了保證合並reducer過程中,每個模塊管理的狀態信息不會相互沖突 * redux在合並的時候容器中的狀態進行分開管理(一合並reducer時候設置的屬性名作為狀態 * 划分的屬性名,把各個版塊管理的狀態放到自己的屬性下 * state={ * vote:{ n:0, m:0 * }, * personal:{ * baseInfo:{} * } * } * store.get-state().vote.n以后獲取狀態信息的時候,也需要把vote加上 * */ import {combineReducers} from 'redux'; import vote from './vote'; import personal from './personal'; let reducer=combineReducers({ vote, personal }); export default reducer; personal.js ====== import * as TYPE from '../action-types'; export default function vote(state = { baseInfo:{} }, action) { //... return state; } vote.js ===== //vote版塊的reducer // state: 原始redux管理的狀態管理(設置初始值) // action: dispatch派發的時候傳遞的行為對象(type,) import * as TYPE from '../action-types'; //把模塊中所有導出的內容全部導出,並重新命名為type export default function vote(state = { n: 0, m: 0, }, action) { switch (action.type) { case TYPE.vote_support: state = {...state, n: state.n + 1}; break; case TYPE.vote_against: state = {...state, m: state.m + 1}; break; } return state; }src/index.js
import React from 'react'; import ReactDOM from 'react-dom'; import 'bootstrap/dist/css/bootstrap.css' import Vote from './component/Vote/Vote'; import store from './store'; ReactDOM.render(<main> <Vote title={'英格蘭對戰巴拿馬,合力必勝'} count={{ n: 100, m: 78 }} store={store}/> </main>, document.querySelector('#root'));component/Vote
Vote.js ===== render() { let {store} = this.props; return <section className={'panel-heading'} style={{width: '50%', height: '20px auto'}}> <VoteHead title={this.props.title}/> <VoteBody store={store}/> <VoteFooter store={store}/> </section> } VoteBody.js ===== import React from 'react'; import PropTypes from 'prop-types'; export default class VoteBody extends React.Component { constructor(props) { super(props); //init state let {n, m} = this.props.store.getState().vote; this.state = {n, m}; } componentDidMount() { this.props.store.subscribe(() => { let {n, m} = this.props.store.getState().vote; this.setState({ n, m }) }); //unsubscribe(): 當前追加的方法移出,接觸綁定的方式 } render() { let {n, m} = this.state, rate = (n / (n + m)) * 100; if (isNaN(rate)) { rate = 0; } return <div className={'panel-body'}> 支持人數: <span>{n}</span> <br/> 反對人數: <span>{m}</span> <br/> 支持比率: <span>{rate.toFixed(2) + '%'}</span> </div>; } } VoteFooter.js ===== import React from 'react'; import PropTypes from 'prop-types'; import action from '../../store/action'; export default class VoteFooter extends React.Component { constructor(props, context) { super(props, context); } render() { let {store: {dispatch}} = this.props; return <div className={'panel-footer'}> <button className={'btn btn-success'} onClick={() => { this.props.store.dispatch(action.vote.support()) }} >支持 </button> <button className={'btn btn-danger'} onClick={ () => { this.props.store.dispatch(action.vote.against()) } }>反對 </button> </div> } }
react-redux
react-redux 是把redux進一步封裝,適配react項目,讓redux操作更簡潔,store文件夾中內容和redux一模一樣
在組件調取使用的時候可以優化一些步驟
Provider 根組件
- 當前整個項目都在Provider組件下,作用就是把創建的store可以供內部組件使用(基於上下文)
- Provider 組件中只允許出現一個組件
- 把創建的store基於屬性傳遞給Provider(這樣后代組件中都可以使用這個store)
connect 高階組件
相對傳統的redux,我們做的步驟優化
導出的不在是我們創建的組件,而是基於connect構造后的高階組件
```js export default connect([mapStateToProps],[mapDispatchToProps])(自己創建的組件) ```以前我們需要自己基於subscribe向事件池追加方法,以達到容器狀態信息改變,執行我們追加的方法,重新渲染組件的目的,但是現在不用了,react-redux幫我們做了這件事:'所有用到redux容器狀態信息的組件,都會向事件池中追加一個方法,當狀態信息改變,通知方法執行,把最新的狀態信息作用屬性傳遞給組件,組件的屬性值改變值改變了,組件也會重新渲染
////把redux容器中的狀態信息遍歷,賦值給當前組件的屬性(state)
let mapStateToProps=state=>{
//state就是redux容器中狀態信息
//我們返回的是啥,就把它掛載到當前組件的屬性上(redux存儲很多信息,我們想用啥就返回啥即可)
return {
...state.vote
}
};
//把redux的dispatch 派發行為遍歷,也復制給組件的屬性(ActionCreator)
let mapDispatchToProps=dispatch=>{
//dispatch:store中存儲的dispatch方法
//返回的是啥,就想當於把啥掛載到組件的屬性上(一般我們掛載一些方法,這
// 些方法完成dispatch派發信息
return {
init(initData) {
dispatch(action.vote.init(initData));
}
}
};
export default connect([mapStateToProps],[mapDispatchToProps])(VoteBase)
=======================
export default connect({state=>({...state.vote})},action.vote)(VoteBase)
react-redux把action-creator中編寫方法(返回action對象的方法),自動構造dispatch派發任務的方法,也就是mapDispatchToProps這種格式把redux容器中的狀態信息遍歷,賦值給當前組件的屬性(state)
[todo](file:\H:\珠峰\2018年第二期源碼、筆記\2018年第二期源碼、筆記\WEEK12\day2)實例
單頁面應用(SPA)多頁面應用(MPA)
-
多頁面應用(MPA)
一個項目由很多頁面組成,使用這個產品,主要就是頁面之間的跳轉(pc端多頁面應用居多);
基於框架開發的時候,需要在webapck中配置多入口,每一個入口對應一個頁面;
-
單頁面應用(SPA)
只有一個頁面,所有需要展示的內容都在這一個頁面中實現切換,webapck只需要配置一個入口即可
移動端單頁面應用居多或者pc端系統類也是單頁面應用為主
-
如何實現單頁面應用
弊端: 由於首頁中的內容包含了所有模塊的信息,所以第一次加載速度很慢
解決vue/react 實現模塊化組件化開發,基於他們提供的路由實現SPA單頁面應用,基於webpack打包
路由
yarn add react-router-dom
BrowerRouter
瀏覽器路由
基於H5中history API(pushState,replaceState,popstate)來保持url和ui的同步
真實項目中應用不多,一般只有當前是基於服務器渲染的,我們才會使用瀏覽器路由
<BrowserRouter basename="/calendar"> <Link to="/today" /> </BrowserRouter> //最后呈現的地址 /calendar/todaybasename:string
- 所有位置的基准URL,basename的正確格式是前面有一個斜杠,但是尾部不能有
getUserConfirmation:func (這個有點不太懂)
用於確認導航函數,默認使用
window.confirm// 使用默認的確認函數 const getConfirmation = (message, callback) => { const allowTransition = window.confirm(message) callback(allowTransition) } <BrowserRouter getUserConfirmation={getConfirmation}/>keyLength:number
- location.key的長度,默認是6
<BrowserRouter keyLength={12} />children:node
單個子元素
HashRouter
哈希路由
真實項目中(前后端分離的項目:客戶端渲染),我們經常使用哈希路由來完成的
基於原生js構造了一套類似於history API的機制,每一次路由切換都是基於window.location.hash 完成的
- hash-router只能出現一個子元素
route
- path:設置匹配地址,但是默認不是嚴格匹配,當前頁面哈希地址只要包含完整的它(內容是不變的,都能匹配上)
- path='/' 和它匹配的只有要斜桿就可以
- component :一旦哈希值和當前router的path相同了,則渲染component執行的組件
- exact: 讓path的匹配嚴謹一些
- strict:不常用,但是要要加
/- render: 權限校驗,渲染組件之前驗證是否存在權限,不存在做一些特殊處理
Switch組件可以解決這個問題,和switch case 一樣,只要有一種校驗成功,就不再向后校驗 <HashRouter> <Switch> <Route path={'/'} exact component={A}>AAA</Route> <Route path={'/user'} component={B}/> <Route path={'/pay'} render={() => { let flag = localStorage.getItem('flag'); if (flag && flag === 'safe') { return <C/> } return '權限不安全'; }}/> //上述都設置完成后,會在末尾設置一個匹配:以上都不符合的情況下,我們路由地址是違法的 //(不設置path就是匹配所有的地址規則) <Route render={ ()=>{ return <div>404</div> }}/> //重定向 //to [string] // <Redirect to='/'/> </Switch> </HashRouter>basename:string 所有位置的基准URL
Link
react-router提供的路由切換組件
原理: 基於Link組件渲染,渲染后的結果就是A標簽,To對應的信息最好變成href中的內容
to:string 一個字符串連接
to:object 一個對象的時候,下面屬性
pathname- 要鏈接到的路徑search- 查詢參數hash- URL 中的 hash,例如 #the-hashstate- 存儲到 location 中的額外狀態數據<Link to={{ pathname: '/courses', search: '?sort=name', hash: '#the-hash', state: { fromDashboard: true } }} />replace:bool(默認是false) 替換history stack中當前的地址(true),還可以追加一個新的地址(false)
react-router中提供的組件必須在
HashRouter或者BrowerRouter里面
NavLink
跟link類似,都是為了實現路由切換跳轉的,不同在於,nav-link組件在當前頁面hash和組件對應地址相吻合的時候,會默認給組件加一個active樣式,讓其選中態
Nav-Link不是點擊誰,誰有選中的樣式(但是可以路由切換),當前頁面哈希后的地址和Nav-Link中的to進行比較,哪個匹配了,哪個才有選中的樣式
to和replace等屬性都有,用法一樣
activeClassName:把默認的active樣式改為自己設定的
activeSyle:把匹配的這個nav-link設置行內樣式
exact & strict控制匹配的時候是否是嚴格匹配
isActive: 匹配后執行對應的函數
with-Router
把一個非路由管控的組件,模擬成為路由管控的組件
export default withRouter(connect()(組件)); 先把nav基於connect高階一下,返回的是一個代理組件proxy, 把返回的代理組件受路由管控受路由管控組件的一些特點:
- 只有當前頁面的哈希地址和路由指定的地址匹配,才會把對應的組件渲染(with-router是沒有地址匹配,都被模擬成為受路由管控的)
- 路由切換的原理,凡是匹配的路由,都會把對應的組件內容,重新添加到頁面中,相反,不匹配的都會在頁面中移出掉,下一次重新匹配上,組件需要重新渲染到頁面中,每一次路由切換的時候(頁面的哈希路由由地址改變),都會從一級路由開始重新校驗一遍
所有受路由管控的組件,在組建的屬性props上默認添加了三個屬性
history
push 向池子中追加一條新的信息,達到切換到指定路由地址的目的
this.props.history.push('/plan')js中實現路由切換go 跳轉到指定的地址(傳的是數字 0當前 -1上一個 -2上兩個...)
go-back =>go(-1) 回退到上一個地址
go-forward =>go(1) 向前走一步
location 獲取當前哈希路由渲染組件的一些信息
- pathname: 當前哈希路由地址
- search : 當前頁面的問號傳參值
- state: 基於redirect/link/nav-link中的to,傳遞的是一個對象,對象中編寫state,就可以再location.state中獲取
match :獲取當前路由匹配的一些結果
- params: 如果當前路由匹配的是地址路徑參數,則這里可以獲取傳遞參數的值
Redirect
重定向3xx
to:string 要重定向的地址
to:object 要重定向的位置
<Redirect to={{ pathname: '/login', search: '?utm=your+face', state: { referrer: currentLocation } }} />push 重定向會將新的位置推入歷史記錄
<Redirect push to="/somewhere/else" /><Redirect from={'/custom'} to={'/custom/list'}/> 當請求的是/custom的時候,直接跳轉到/custom/list的路由地址
qs
yarn add qs
問號傳參
OA和ERP
OA:企業辦公管理系統(偏向於有助於日常辦公)
ERP: 企業戰略資源管理系統(偏向於有管理思想)
- 釘釘
- TAPD
- 今目標
- 紛享銷客
CRM:客戶管理系統
CMS:內容管理系統(內容分發平台)

IM:即時通信系統
redux中間件
redux-logger:能夠在控制台清晰的展示當前redux操作的流程和信息(原有狀態,派發信息,修改后的狀態)
redux-thunk: 處理異步的dispatch派發
redux-promise: 在dispatch派發的時候支持promise操作
yarn add redux-logger redux-thunk redux-promise
store/index.js
=====
import {createStore, applyMiddleware} from 'redux';//applyMiddleware導入中間件
import reduxLogger from 'redux-logger';
import reduxThunk from 'redux-thunk';
import reduxPromise from 'redux-promise';
import reducer from './reducer';
let store = createStore(reducer, applyMiddleware(reduxLogger, reduxThunk, reduxPromise));
export default store;
//=>PROMISE中間件的語法
create(payload) {
return {
type: TYPES.CUSTOM_CREATE,
//=>傳遞給REDUCER的PAYLOAD需要等待PROMISE成功,把成功的結果傳遞過去
payload: new Promise(resolve => {
setTimeout(() => {
resolve(payload);
}, 3000);
})
}
}
create(payload) {
//=>THUNK中間件的使用語法:在指定執行派發任務的時候,等待3000MS后在派發
return dispatch => {
//=>DISPATCH都傳遞給我們了,我們想什么時候派發,自己搞定即可
setTimeout(() => {
dispatch({
type: TYPES.CUSTOM_CREATE,
payload
});
}, 3000);
};
}
[路由問號傳參案例](file:\H:\珠峰\2018年第二期源碼、筆記\2018年第二期源碼、筆記\WEEK12\day3\src)
Ant Desion UI框架
yarn add antd;
復習
{React.createElement('a',{href:'http://www.baidu.com'},'Hello')}
//標簽 屬性 子元素
class HelloMessage extends Component{
render(){
let child=React.createElement('li',{className:'ddd'},'我是子頁面');
return <div>Hello {this.props.name}
{React.createElement('a',{href:'http://www.baidu.com'},'Hello')}
<br/>
{React.createElement('ul',{className:'ccc'},child
)}
</div>
}
}
style 屬性應該由CSS屬性構成的JS對象
* className=''
* style={{fontSize:50,backgroundColor:'red'}} // zIndex 多峰命名
let styles={
fontSize:50,
fontWeight: 'bold',
backgroundColor: 'red',
};
{{styles}}
模擬 if
{this.state.tags.length === 0 && '等於0'} //A為真返回B
renderTags(){
if(this.state.tags.length===0) return <p>里面沒有元素</p>
return <ul>
{this.state.tags.map((tag,index)=><li key={index}>{tag}</li>)}
<hr />
</ul>
}
{this.renderTags()}
props 屬性
state 組件的狀態 可以通過 this.setState進行更改
無狀態組件
const Hellos = (props) => <div>Hello {props.name}</div>;
<Hellos name={'zhangsan'}/>
有狀態組件
//定義一個時間的方法,掛載前開始定時器執行這個方法,卸載后清除掛載前的那個定時器方法
export default class LikeButton extends React.Component {
constructor(props) {
super(props);
//初始化狀態
this.state = {
data: new Date()
}
};
componentDidMount(){
this.timerId=setInterval(
()=>this.tick()
)
}
//方法
tick(){
this.setState({
data:new Date()
})
}
componentWillUnmount(){
clearInterval(this.timerId);
}
render() {
return <div>
<h3>{this.state.data.toLocaleTimeString()}</h3>
</div>
}
}
props 對於使用他的組件來說是只讀的,只能通過父組件進行修改
state 可以通過 this.setState({ }) 進行修改
注意不要在push pop shift unshift splice 等方法修改
應該用concat slice filter 會放回一個新數組
原生事件
可以再componentDidMount方法中通過addEventListener 綁定瀏覽器原生事件
componentWillUnmount 方法解除 removeEventListener
在dom 中 設置 ref屬性指定一個名稱 通過 this.refs.指定名稱獲取
組合組件
父組件 <Avatar username="pwh" />
const Avatar = (props) => {
return (
<div>
//子組件 通過屬性傳遞
<ProfilePic username={props.username} />
<ProfileLink username={props.username} />
</div>
);
}
循環的時候必須要給key
// arr是在父組件里面聲明的arr數組的屬性
<ul>{this.props.arr.map((v,i)=>{
return <li key={i}>{v}</li>
})}</ul>
組件標簽里面包含的子元素通過 this.props.children
<LikeButton username={'pwh'} arr={[1,2,3,4]}>
<span>12123</span>
<p>我是一個p標簽</p>
</LikeButton>
props.children通常是一個組件對象的數組,當 props.children是唯一的子元素,那就不是數組
點擊事件的內聯樣式
onClick={ ()=>(console.log(1))}
第二種方法
onClick={this.handleClick} //記得不要再這里帶(e)參數 會報錯的
<div onClick={this.handleClick.bind(this)}>${this.props.name}</div>
handleClick=(e)=>{
console.log(e.target.innerHTML);
}; //函數建議使用箭頭函數的寫法
通過 setState來修改state
this.setState({
count: this.state.count+1
})
store有四個方法。 getState: 獲取應用當前 state。 subscribe:添加監聽器。 dispatch:分發 action。更新 state。 replaceReducer:替換 store 當前用來處理 state 的 reducer。 兩者的關系是: state=store.getState() 常用的是dispatch,這是修改State的唯一途徑,使用起來也非常簡單。 import {createState} from 'redux'; function counter(state=0,action) { switch(action.type){ case 'INCREMENT': return state + 1; case 'DECREMENT': return state-1 default: return state } } let store=createState(counter); store.subscribe(()=>{ console.log(store.getState()); }); store.dispatch({type:"INCREMENT"}); action 唯一的約束僅僅就是包含一個typestore由redux的createStore(reducer)生成的 state通過store.getState()獲取的 action本質上是一個包含type屬性的普通對象 改變 state必須dispatch一個action reducer 本質上action.type 來更新的 實際上state就是所有reducer返回的匯總 redux有五個API createStore(reducer,[]) combineReducers(reducers) applyMiddleware(...middlewares) bindActionCreators(actionCreatore,dispatch) compose(...functions) Redux 強調三大基本原則 * 唯一數據源 * 保持狀態只讀 * 數據改變只能通過純函數完成過程總結
創建一個操作指令action ->創建一個reducer -> 通過createStore(reducer) 創建一個store
通過store dispath(action) 執行reducer 中更新操作,更新store中的數據
學習redux
我們大多數人的學習過程一般是——一個循序漸進、逐步迭代的過程,而redux的學習卻不是這樣,你不看概念,就沒法理解示例demo,你不敲示例demo,你就無法完全理解redux的各種基礎概念。所以最終發現,redux的最好的學習方式就是,通讀一個個的概念,敲出示例demo,再根據示例demo,反過來理解概念,循環往復,總能學的懂!!
const todo={ TOGGLE_TODO:'TOGGLE_TODO', GET_TODOS:'GET_TODOS', toggleTodo({ items,id }) { return { type: todo.TOGGLE_TODO, items:items, id:id } }, getTodos({items}){ return { type:todo.GET_TODOS, items:items } } }; export default todo; // ====== export default function(state = {默認值}, action) { switch(action.type){ case GET_TODOS: return {todos:[...action.items]} case TOGGLE_TODO://deleted=true return { todos:action.items.map((i)=>{ if(i.id===action.id){ return { ...i, status:i.status===0?1:0 } }else{ return { ...i } } }) } default: return state; } } const store = createStore(todo);
redux-hook
dva.js
npm install dva-cli -gput 用於觸發action
put({type:types.ADD})call 用於調用異步邏輯,支持promise
call(第一個參數是一個方法,第二個是傳入這個方法的參數)
take 等待dispatch 匹配某個action
yield 方法(傳入方法的參數) //跟上面等同
run() 方法是添加功能 * yield
effect 都是一個簡單對象
select 用於從state里獲取數據
定義路由 router/Products.js import React from 'react'; const Products = (props) => ( <h2>List of Products</h2> ); export default Products; 添加路由 router.js <Route path='/products' exact component={Products}/>編寫組件 components/編寫組件.js 定義Model model/..js export default { namespace:'count', state:0, reducers:{ add(count){return count+1}, minus(count){return count-1} } } 在indexjs 載入 app.model(require('./models/products').default); function IndexPage(props) { return ( <div> <h3> {props.count}</h3> <button key={'add'} onClick={()=>{props.dispatch({type:'count/add'})}}>+</button> <button key={'munus'} onClick={()=>{props.dispatch({type:'count/minus'})}}>-</button> </div> ); } IndexPage.propTypes = {}; export default connect(({count}) => ({count}))(IndexPage);react react-redux react-saga
react-sage是更好的處理異步
function* g1() { yield 2; yield 3; yield 4; } function* g2() { yield 1; yield* g1(); yield 5; } var iterator = g2(); console.log(iterator.next()); // { value: 1, done: false } console.log(iterator.next()); // { value: 2, done: false } console.log(iterator.next()); // { value: 3, done: false } console.log(iterator.next()); // { value: 4, done: false } console.log(iterator.next()); // { value: 5, done: false } console.log(iterator.next()); // { value: undefined, done: true } yield* 表達式用於委托給另一個generator 或可迭代對象。yarn add roadhog
UMI.js
全局安裝umi cnpm install -g umi
路徑似路由
umi div 執行
umi g創建一些頁面umi g page index 是根路徑 創建 umi 項目 yarn create 項目名import React from 'react' import dva from 'dva' import Counter from './Counter' //dva 是一個函數 通過執行它可以拿到一個app 對象 let app = dva(); // function delay(ms) { // return new Promise(function (resolve, reject) { // setTimeout(function () { // resolve() // }, ms) // }) // } // // function getAmount() { // return fetch('http://localhost:3000/amount').then(res => res.json()); // } //app.router app.start() app.model //一個模板就是一個狀態,然后把reducer和狀態寫在一起 //添加一個模型 app.model({ //命名空間: 因為一個應用會有很多個模型,每一個模型有一個對象 namespace: 'counter', //此命名空間的默認狀態 state: {current: 0, height: 0}, //他是用來接收action ,修改倉庫狀態 reducers: { //reducer接受老狀態和動作,返回新狀態 //state老狀態(parameter) action:AnyAction add(state, action) { // let current = state.current + action.payload; // return {current, height: current > state.height ? current : state.height} return {current: state.current + 1} }, // minus(state, action) { // return {...state, current: state.current - action.payload} // } }, //它是用來執行副作用的,比如說異步操作,調用api接口 effects: { //表示這是一個generator effect=redux-saga/effects // * add(action, {call, put}) { // //表示這是一個generator // //amount 是接口中的變量 call 調用方法 // let {amount}=yield call(getAmount); // //type;'方法' // yield put({type:'add',payload:amount}) // // yield call(delay, 1000); // // yield put({type: 'minus'})//可以不加前綴,counter/minus/派發其他的可以寫 // } }, }); //參數是一個函數,此應用本身就是要熏染函數的返回值 app.router(() => <Counter />); //本質是啟動應用,就是通過app.router獲取組件,通過ReactDOM渲染到容器上 app.start('#root'); import React from 'react'; import {connect} from 'dva'; import './Counter.css'; class Counter extends React.Component { render() { return (<div> <div className={'container'}> <p>當前記錄{this.props.current}</p> <button onClick={() => this.props.dispatch({type: 'counter/add'})}>add</button> </div> </div>) } } export default connect( state => state.counter )(Counter);


