SPA(Single Page Application)
單頁(yè)面應(yīng)用,就是只有一張Web頁(yè)面的應(yīng)用。單頁(yè)應(yīng)用程序 (SPA) 是加載單個(gè)HTML 頁(yè)面并在用戶與應(yīng)用程序交互時(shí)動(dòng)態(tài)更新該頁(yè)面的Web應(yīng)用程序。
單頁(yè)面應(yīng)用的優(yōu)點(diǎn)
- 最大的好處是用戶體驗(yàn),對(duì)于內(nèi)容的改動(dòng)不需要加載整個(gè)頁(yè)面。
- 數(shù)據(jù)層和UI的分離,可以重新編寫(xiě)一個(gè)原生的移動(dòng)設(shè)備應(yīng)用程序而不用大動(dòng)干戈(同一套后端程序代碼,不用修改就可以用于Web界面、手機(jī)、平板等多種客戶端)。
- 高效。它對(duì)服務(wù)器壓力很小,消耗更少的帶寬,能夠與面向服務(wù)的架構(gòu)更好地結(jié)合。
單頁(yè)面應(yīng)用的缺點(diǎn)
- 不利于SEO
- 初次加載耗時(shí)增多
- 導(dǎo)航不可用;前進(jìn)、后退、地址欄等,需要程序進(jìn)行管理;
書(shū)簽,需要程序來(lái)提供支持;
應(yīng)用場(chǎng)景
那么單頁(yè)應(yīng)用的應(yīng)用如何呢?看了一些資料,總覺(jué)出來(lái)單頁(yè)面應(yīng)用有兩個(gè)硬傷:
首屏加載慢(大量js導(dǎo)致首屏加載慢)、seo不友好
如何應(yīng)用SPA或者是否應(yīng)用SPA,大概需要考慮以下這幾點(diǎn):
- 交互體驗(yàn)
不同的應(yīng)用面對(duì)不同人群,會(huì)有不同的交互體驗(yàn)需求。 - 工程代價(jià)
大型網(wǎng)站轉(zhuǎn)spa會(huì)有很大的代價(jià)。 - 容錯(cuò)問(wèn)題
SPA所有腳本都加進(jìn)來(lái),如果出現(xiàn)一個(gè)JS錯(cuò)誤,那很可能整個(gè)網(wǎng)站就掛掉了,風(fēng)險(xiǎn)很大。 - 是否必要
簡(jiǎn)單呈現(xiàn)內(nèi)容的網(wǎng)站,沒(méi)有必要用spa。增加了開(kāi)發(fā)和調(diào)試的復(fù)雜性,但是除了效果更酷炫點(diǎn),沒(méi)有多少實(shí)用價(jià)值。 - 是否需要兼容低版本的ie瀏覽器
SPA總結(jié)
綜合了解了這種SPA單頁(yè)應(yīng)用和傳統(tǒng)的多頁(yè)面應(yīng)用,在以后的開(kāi)發(fā)中,我可能會(huì)采取單頁(yè)和多頁(yè)相結(jié)合的方式,該跳轉(zhuǎn)的地方還是跳轉(zhuǎn),結(jié)合單頁(yè)模式的用戶體驗(yàn)優(yōu)點(diǎn),將用戶體驗(yàn)發(fā)揮到極致,因?yàn)槲矣X(jué)得用戶體驗(yàn)是最重要的東西之一。
項(xiàng)目安裝的模塊解釋
開(kāi)發(fā)依賴模塊:
- autoprefixer:postcss-loader的一個(gè)插件,使用一個(gè)數(shù)據(jù)庫(kù)根據(jù)當(dāng)前瀏覽器的普及度以及屬性支持自動(dòng)給你的css添加前綴前綴:詳情點(diǎn)這里
- babel-core:babel轉(zhuǎn)碼的核心,必須安裝bable詳情點(diǎn)這里(阮一峰)
- babel-loader:babel加載器,配置babel編譯必備
- babel-plugin-add-module-exports:babel對(duì)export default{}支持不好,不想寫(xiě)成module.exports就需要安裝點(diǎn)這里
- babel-plugin-react-transform:代替react-hot-loader的插件,是基于Babel Plugin的。這是一個(gè)基本的架子,要實(shí)現(xiàn)熱替換還要安裝其他插件。
- react-transform-hmr:安裝這個(gè)才能實(shí)現(xiàn)熱替換的功能。
- babel-preset-es2015:babel轉(zhuǎn)譯預(yù)設(shè)規(guī)則(轉(zhuǎn)es5)
- babel-preset-react:babel轉(zhuǎn)譯預(yù)設(shè)規(guī)則(react的jsx)
- css-loader:允許引入css文件
- style-loader:為了在html中以style的方式嵌入css
- postcss-loader:一個(gè)插件平臺(tái),這里只要用其autoprefixer功能
- eslint-loader:代碼規(guī)范檢查點(diǎn)這里
- extract-text-webpack-plugin:分離css文件
- url-loader:圖片與字體加載器,file-loader的上層封裝,依賴file-loader
- file-loader:圖片與字體加載器
- html-webpack-plugin:這樣可以將輸出的文件名自動(dòng)注入到html中,不用我們自己手寫(xiě)
- json-loader:處理json文件
- koa:node框架
- koa-router:koa路由
- less:less編譯css
- less-loader:less加載器
- open-browser-webpack-plugin:打包完成自動(dòng)打開(kāi)瀏覽器的插件
- webpack:一代神器
- webpack-dev-server:一個(gè)小型的Node.js Express服務(wù)器,可實(shí)現(xiàn)代碼修改自動(dòng)看這里
上線依賴模塊:
- es6-promise:使用fetch時(shí)為了兼容老版本需要安裝
- immutable:react性能優(yōu)化,需要學(xué)習(xí)新的APIimmutable
- react:
- react-addons-css-transition-group:實(shí)現(xiàn)組件出現(xiàn)與消失的css3過(guò)渡動(dòng)畫(huà)官方地址
- react-addons-pure-render-mixin:用以替換shouldComponentUpdate,優(yōu)化性能
- react-dom:
- react-redux:
- react-router:
- react-swipe:輪播圖插件,引入swipe-js-iso,創(chuàng)建reat組件
- swipe-js-iso:基于swipe.js的一個(gè)Pull Request
- redux:
- whatwg-fetch:fetch
webpack配置詳解
resolve
定義了解析模塊路徑時(shí)的配置,常用的就是extensions;可以用來(lái)指定模塊的后綴,這樣在引入模塊時(shí)就不需要寫(xiě)后綴
resolve:{extensions:['', '.js','.jsx']}
postcss
在加載css/less時(shí),用到postcss,主要使用autoprefixer功能,能自動(dòng)加css3的瀏覽器前綴;
postcss:[
require('autoprefixer)//調(diào)用autoprefixer插件,例如display:flex 針對(duì)不同品牌及版本的瀏覽器hack前綴
]
html-webpack-plugin
html模板插件
var HtmlWebpackPlugin = require('html-webpack-plugin');
plugins:[
new HtmlWebpackPlugin({template:'./app/index.html'})
]
webpack.HotModuleReplacementPlugin
var webpack = require('webpack');
plugins:[
new webpack.HotModuleReplacementPlugin()
]
open-browser-webpack-plugin
var OpenBrowserPlugin = require('open-browser-webpack-plugin');
plugins:[
new OpenBrowserPlugin({url:'http://localhost:8080'})
]
DefinePlugin
可在業(yè)務(wù)js代碼中使用 DEV 判斷是否是dev模式(dev模式下可以提示錯(cuò)誤、測(cè)試報(bào)告等, production模式不提示,在package.json配置的dev腳本命令中定義了NODE_ENV的值,所以這里可以獲取到,也可以直接寫(xiě)'true')
var webpack = require('webpack');
plugins:[
new webpack.DefinePlugin({__DEV__:JSON.stringify(JSON.parse((process.env.NODE_ENV == 'dev') || 'false'))})
]
webpack-dev-server 代理
devServer: {
proxy: {
// 凡是 `/api` 開(kāi)頭的 http 請(qǐng)求,都會(huì)被代理到 localhost:3000 上,由 koa 提供 mock 數(shù)據(jù)。
// koa 代碼在 ./mock 目錄中,啟動(dòng)命令為 npm run mock
'/api': {
target: 'http://localhost:3000',
secure: false
}
},
contentBase: "./public", //本地服務(wù)器所加載的頁(yè)面所在的目錄
colors: true, //終端中輸出結(jié)果為彩色
historyApiFallback: true, //不跳轉(zhuǎn)
inline: true, //實(shí)時(shí)刷新
hot: true // 使用熱加載插件 HotModuleReplacementPlugin
}
ExtractTextPlugin
webpack.production.config中配置,實(shí)現(xiàn)上線css與js代碼分離
var ExtractTextPlugin = require('extract-text-webpack-plugin');
loaders: [
{ test: /\.less$/, exclude: /node_modules/, loader: ExtractTextPlugin.extract('style', 'css!postcss!less') },
{ test: /\.css$/, exclude: /node_modules/, loader: ExtractTextPlugin.extract('style', 'css!postcss') }
]
plugins:[
new ExtractTextPlugin('[name].[chunkhash:8].css')
]
vender
entry: {
app: path.resolve(__dirname, 'app/index.jsx'),
// 將 第三方依賴 單獨(dú)打包
vendor: [
'react',
'react-dom',
'react-redux',
'react-router',
'redux',
'es6-promise',
'whatwg-fetch',
'immutable'
]
}
output: {
path: __dirname + "/build",
filename: "[name].[chunkhash:8].js",
publicPath: '/'
}
plugins:[
//提供公共代碼
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: '[name].[chunkhash:8].js'
})
]
import React from 'react'引用過(guò)程?
npm 安裝的 react 的物理文件是存放在 ./node_modules/react中的,打開(kāi)./node_modules/react/package.json找到"main": "react.js",,這里的main即指定了入口文件,即./node_modules/react/react.js這個(gè)文件。
react開(kāi)發(fā)中的代碼分離
- page層:按頁(yè)面拆分,每個(gè)頁(yè)面有一個(gè)主頁(yè)面index.jsx
- subPage層:對(duì)于復(fù)雜頁(yè)面,要將一個(gè)頁(yè)面拆封成多個(gè)子頁(yè),不復(fù)雜的頁(yè)面只寫(xiě)在index.jsx里即可
- component層:只用來(lái)展示數(shù)據(jù)的組件,對(duì)于不同頁(yè)面內(nèi)相同的組件,寫(xiě)在component層以便復(fù)用
react生命周期
組件在初始化的時(shí)候,會(huì)觸發(fā)5個(gè)鉤子函數(shù):
getDefaultProps()
設(shè)置默認(rèn)的props,也可以用dufaultProps設(shè)置組件的默認(rèn)屬性。getInitialState()
在使用es6的class語(yǔ)法時(shí)是沒(méi)有這個(gè)鉤子函數(shù)的,可以直接在constructor中定義this.state。此時(shí)可以訪問(wèn)this.props。componentWillMount()
組件初始化時(shí)只調(diào)用,以后組件更新不調(diào)用,整個(gè)生命周期只調(diào)用一次,此時(shí)可以修改state。render()
react最重要的步驟,創(chuàng)建虛擬dom,進(jìn)行diff算法,更新dom樹(shù)都在此進(jìn)行。此時(shí)就不能更改state了。componentDidMount()
組件渲染之后調(diào)用,可以通過(guò)this.getDOMNode()獲取和操作dom節(jié)點(diǎn),只調(diào)用一次。
更新時(shí)觸發(fā)的5個(gè)鉤子函數(shù):
componentWillReceivePorps(nextProps)
組件初始化時(shí)不調(diào)用,組件接受新的props時(shí)調(diào)用。shouldComponentUpdate(nextProps, nextState)
react性能優(yōu)化非常重要的一環(huán)。組件接受新的state或者props時(shí)調(diào)用,我們可以設(shè)置在此對(duì)比前后兩個(gè)props和state是否相同,如果相同則返回false阻止更新,因?yàn)橄嗤膶傩誀顟B(tài)一定會(huì)生成相同的dom樹(shù),這樣就不需要?jiǎng)?chuàng)造新的dom樹(shù)和舊的dom樹(shù)進(jìn)行diff算法對(duì)比,節(jié)省大量性能,尤其是在dom結(jié)構(gòu)復(fù)雜的時(shí)候。不過(guò)調(diào)用this.forceUpdate會(huì)跳過(guò)此步驟。componentWillUpdate(nextProps, nextState)
組件初始化時(shí)不調(diào)用,只有在組件將要更新時(shí)才調(diào)用,此時(shí)可以修改staterender()
componentDidUpdate()
組件初始化時(shí)不調(diào)用,組件更新完成后調(diào)用,此時(shí)可以獲取dom節(jié)點(diǎn)。
還有一個(gè)卸載鉤子函數(shù)componentWillUnmount()
組件將要卸載時(shí)調(diào)用,一些事件監(jiān)聽(tīng)和定時(shí)器需要在此時(shí)清除。
以上可以看出來(lái)react總共有10個(gè)周期函數(shù)(render重復(fù)一次),這個(gè)10個(gè)函數(shù)可以滿足我們所有對(duì)組件操作的需求,利用的好可以提高開(kāi)發(fā)效率和組件性能。
常用的生命周期在項(xiàng)目中怎么用到?
- comopentDidMount
組件第一次加載時(shí)渲染loading組件,一般在此獲取網(wǎng)絡(luò)數(shù)據(jù),將數(shù)據(jù)賦值給狀態(tài),改變狀態(tài)重新渲染頁(yè)面。實(shí)際開(kāi)始項(xiàng)目開(kāi)發(fā)時(shí),會(huì)經(jīng)常用到。 - shouldComponentUpdate
主要用于性能優(yōu)化,React 的性能優(yōu)化也是一個(gè)很重要的話題。 - componentDidUpdate
組件更新了之后觸發(fā)的事件,一般用于清空并更新數(shù)據(jù)。實(shí)際開(kāi)始項(xiàng)目開(kāi)發(fā)時(shí),會(huì)經(jīng)常用到。 - componentWillUnmount
組件在銷毀之前觸發(fā)的事件,一般用戶存儲(chǔ)一些特殊信息,以及清理setTimeout事件等。
react性能優(yōu)化
- 介紹PureComponent
點(diǎn)這里 - 性能檢測(cè),檢測(cè)優(yōu)化結(jié)果
npm i react-addons-perf --save
// 性能測(cè)試
import Perf from 'react-addons-perf';
if (__DEV__) { window.Perf = Perf }
運(yùn)行程序。在操作之前先運(yùn)行Perf.start()開(kāi)始檢測(cè),然后進(jìn)行若干操作,運(yùn)行Perf.stop停止檢測(cè),然后再運(yùn)行Perf.printWasted()即可打印出浪費(fèi)性能的組件列表。在項(xiàng)目開(kāi)發(fā)過(guò)程中,要經(jīng)常使用檢測(cè)工具來(lái)看看性能是否正常。
- PureRenderMixin 優(yōu)化
React 最基本的優(yōu)化方式
組件中的props和state一旦變化會(huì)導(dǎo)致組件重新更新并渲染,但是如果props和state沒(méi)有變化也的觸發(fā)更新了(這種情況確實(shí)存在,比如調(diào)用setState方法,但狀態(tài)并沒(méi)有改變),這就導(dǎo)致了無(wú)效渲染
import React from 'react' ;
import PureRenderMixin from 'react-addons-pure-render-mixin' ;
class List extends React.Component {
constructor(props, context) {
super(props, context);
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
}
//...
}
重寫(xiě)組件的shouldComponentUpdate函數(shù),在每次更新之前判斷props和state,如果有變化則返回true,無(wú)變化則返回false。
因此,我們?cè)陂_(kāi)發(fā)過(guò)程中,在每個(gè) React 組件中都盡量使用PureRenderMixin
- Immutable.js 優(yōu)化
React 的終極優(yōu)化是使用 Immutable.js 來(lái)處理數(shù)據(jù),Immutable 實(shí)現(xiàn)了 js 中不可變數(shù)據(jù)的概念(可以去查一下何為“不可變數(shù)據(jù)”)。
但是也不是所有的場(chǎng)景都適合用它,當(dāng)我們組件的props和state中的數(shù)據(jù)結(jié)構(gòu)層次不深(例如普通的數(shù)組、對(duì)象等)的時(shí)候,就沒(méi)必要用它。但是當(dāng)數(shù)據(jù)結(jié)構(gòu)層次很深(例如obj.x.y.a.b = 10這種),你就得考慮使用了。
之所以不輕易使用是,Immutable 定義了一種新的操作數(shù)據(jù)的語(yǔ)法,如下。和我們平時(shí)操作 js 數(shù)據(jù)完全不一樣,而且每個(gè)地方都得這么用,學(xué)習(xí)成本高、易遺漏,風(fēng)險(xiǎn)很高。
var map1 = Immutable.Map({a:1, b:2, c:3});
var map2 = map1.set('b', 50);
map1.get('b'); // 2
map2.get('b'); // 50
因此,建議優(yōu)化還是要從設(shè)計(jì)著手,盡量把數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)的扁平一些,這樣既有助于優(yōu)化系統(tǒng)性能,又減少了開(kāi)發(fā)復(fù)雜度和開(kāi)發(fā)成本。
react-router
注意:react-router4.0及以上版本語(yǔ)法有重大改變,老語(yǔ)法會(huì)報(bào)錯(cuò)
<Router history={hashHistory}>
<Route path="/" component={App}>
<IndexRoute component={Home}></IndexRoute>
<Route path="city" component={City}></Route>
<Route path='/Login(/:router)' component={Login}/>
<Route path="user" component={User}></Route>
<Route path="search/:category(/:keyword)" component={Search}></Route>
<Route path="detail/:id" component={Detail}></Route>
<Route path="*" component={NotFound}></Route>
</Route>
</Router>
目錄結(jié)構(gòu)
文件結(jié)構(gòu):
- 入口文件,源碼文件夾app目錄下的index.js
需要用redux傳遞信息的組件用Provider包住
const store = configureStore();
render(
<Provider store={store}>
<Hello/>
</Provider>,document.querySelector('#app')
);
- constants 常量文件夾
定義了action的type的常量,方便修改復(fù)用
export const USERINFO_LOGIN='USERINFO_LOGIN';
export const UPDATE_CITY='UPDATE_CITY';
- components 木偶組件文件夾
負(fù)責(zé)渲染視圖 - store文件夾
創(chuàng)建store的函數(shù),需要引用reducers目錄內(nèi)的rootReducer - reducers文件夾
reducers目錄下有index.js入口文件,可用combineReducers方法引用多個(gè)規(guī)則
import {combineReducers} from 'redux';
import userinfo from './userinfo';
import userinfo2 from './userinfo';
const rootReducer = combineReducers({userinfo,userinfo2});
export default rootReducer;
- actions 文件夾
存放派發(fā)方法
import * as actionTypes from '../constants/userinfo';
export function login(data) {
return {type:actionTypes.USERINFO_LOGIN,data}
}
export function updateCity(data) {
return {type: actionTypes.UPDATE_CITYNAME, data}
}
- containers 頁(yè)面文件夾
引入constants,引入connect方法和bindActionCreators方法:
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import * as userinfoActions from '../actions/userinfo';
connect方法最后需要把組件包裝一下再輸出,目的是將派發(fā)后的狀態(tài)(userinfo)以及派發(fā)時(shí)的actions(userinfoActions)當(dāng)作組件的props傳遞給組件,userinfo是狀態(tài)數(shù)據(jù),組件根據(jù)userinfo渲染視圖,userinfoActions是觸發(fā)狀態(tài)改變的方法,讓組件某事件綁定該方法后就可以有改變狀態(tài)的能力:
function mapStateToProps(state) {//一個(gè)自定義函數(shù),最為connect方法的第一個(gè)參數(shù)
console.log(state);
return {userinfo:state.userinfo}; //我的理解:state為rootReducer狀態(tài),state.userinfo為rootReducer下的userinfo狀態(tài),rootReducer下可以掛載多個(gè)規(guī)則;state打印結(jié)果為一個(gè)實(shí)例對(duì)象,里面有對(duì)應(yīng)的各個(gè)規(guī)則以及其當(dāng)前的狀態(tài)
}
function mapDispatchToProps(dispatch) {//一個(gè)自定義函數(shù),最為connect方法的第二個(gè)參數(shù)
return {userinfoActions:bindActionCreators(userinfoActions,dispatch)}
}
export default connect(mapStateToProps,mapDispatchToProps)(Hello);
組件渲染完成后,觸發(fā)redux狀態(tài)改變,再重新渲染組件
componentDidMount(){
this.props.userinfoActions.login({
userid:'aaa',
city:'北京'
})
}
}
fetch
- jquery 不考慮兼容,做dom查詢,事件綁定,效果處理
react開(kāi)發(fā)為了用ajax函數(shù)去引用jq不值當(dāng),而且js中ajax有一個(gè)詬病:復(fù)雜業(yè)務(wù)下callback的嵌套問(wèn)題;fetch是一種可替代ajax獲取/提交數(shù)據(jù)的技術(shù),有些高級(jí)瀏覽器已經(jīng)可以window.fetch使用,相比與$.ajax更輕量,且原生支持promise,更符合現(xiàn)在的編程習(xí)慣 - 解決異步嵌套問(wèn)題除了promise還有2個(gè)方法:
1.es6的generator函數(shù) 2.es7的async,await - fetch的坑
http://blog.csdn.net/whbwhb1/article/details/53322451
options = {
catchs: 異常處理,控制臺(tái)拋出的異常是否自己處理:true 是,false 否 由公共方法統(tǒng)一處理優(yōu)化顯示給用戶 默認(rèn) false
credentials: 請(qǐng)求帶上cookies,是每次請(qǐng)求保持會(huì)話一直
method: 請(qǐng)求使用的方法,如 GET、POST
headers: 請(qǐng)求的頭信息,形式為 Headers 對(duì)象或 ByteString。
body: 請(qǐng)求的 body 信息:可能是一個(gè) Blob、BufferSource、FormData、URLSearchParams 或者 USVString 對(duì)象。注意 GET 或 HEAD 方法的請(qǐng)求不能包含 body 信息。
mode: 請(qǐng)求的模式,如 cors、no-cors 或者same-origin。是否允許跨域請(qǐng)求
cache: 請(qǐng)求的 cache 模式: default, no-store, reload, no-cache, force-cache, or only-if-cached.
}
es6-promise.js可以使它很好的支持IE9以上的版本,IE8 需要改fetch.js源碼才能支持(見(jiàn)上一網(wǎng)址博客)
前端也需要掌握http
前端涉及到很多的數(shù)據(jù)操作:數(shù)據(jù)的獲取,數(shù)據(jù)的提交,數(shù)據(jù)的安全性,數(shù)據(jù)性能的優(yōu)化
數(shù)據(jù) Mock
在目前互聯(lián)網(wǎng)行業(yè) web 產(chǎn)品開(kāi)發(fā)中,前后端大部分都是分離開(kāi)發(fā)的,前端開(kāi)發(fā)過(guò)程中無(wú)法實(shí)時(shí)得到后端的數(shù)據(jù)。這種情況下,一般會(huì)使用三種方式:
- 模擬靜態(tài)數(shù)據(jù):即按照既定的數(shù)據(jù)格式,自己提供一些靜態(tài)的JSON數(shù)據(jù),用相關(guān)工具(如fis3)做接口來(lái)獲取這些數(shù)據(jù)。該形式使用不比較簡(jiǎn)單的、只用 get 方法的場(chǎng)景,該項(xiàng)目不適用。
- 模擬動(dòng)態(tài)接口:即自己用一個(gè) web 框架,按照既定的接口和數(shù)據(jù)結(jié)構(gòu)的要求,自己模擬后端接口的功能,讓前端項(xiàng)目能順利跑起來(lái)。該方式適用于新開(kāi)發(fā)的項(xiàng)目,后端和前端同時(shí)開(kāi)發(fā)。
- 轉(zhuǎn)發(fā)線上接口:項(xiàng)目開(kāi)發(fā)中,所有的接口直接用代理獲取線上的數(shù)據(jù),post 的數(shù)據(jù)也都直接提交到線上。該方式適用于成熟項(xiàng)目中。
最外層組件的作用
在路由配置中,我們有一個(gè)最外層組件,App:
<Router history={this.props.history}>
<Route path='/' component={App}>
<IndexRoute component={Home}/>
<Route path='/city' component={City}/>
<Route path='/User' component={User}/>
<Route path='/search/:type(/:keyword)' component={Search}/>
<Route path='/detail/:id' component={Detail}/>
<Route path='*' component={NotFound}/>
</Route>
</Router>
其作用是:
- 復(fù)用公共的頭部尾部組件
render() {
return (<div>
<Head/>
{this.props.children}
<Footer/>
</div>
)
}
- 加載loading組件
render() {
return (
<div>{this.state.initDone ? this.props.children : <div>正在加載...</div> }
</div>
)
}
module.exports與exports,export與export default之間的關(guān)系和區(qū)別
- CommonJS模塊規(guī)范
為了方便,Node為每個(gè)模塊提供一個(gè)exports變量,指向module.exports。可以直接在 exports 對(duì)象上添加方法,但是注意,不能直接將exports變量指向一個(gè)值,因?yàn)檫@樣等于切斷了exports與module.exports的聯(lián)系。 - ES6模塊規(guī)范
// 第一組
export default function crc32() { // 輸出
// ...
}
import crc32 from 'crc32'; // 輸入
// 第二組
export function crc32() { // 輸出
// ...
};
import {crc32} from 'crc32'; // 輸入
上面代碼的兩組寫(xiě)法,第一組是使用export default時(shí),對(duì)應(yīng)的import語(yǔ)句不需要使用大括號(hào);第二組是不使用export default時(shí),對(duì)應(yīng)的import語(yǔ)句需要使用大括號(hào)。
export default命令用于指定模塊的默認(rèn)輸出。顯然,一個(gè)模塊只能有一個(gè)默認(rèn)輸出,因此export default命令只能使用一次。所以,import命令后面才不用加大括號(hào),因?yàn)橹豢赡軐?duì)應(yīng)一個(gè)方法。
本質(zhì)上,export default就是輸出一個(gè)叫做default的變量或方法,然后系統(tǒng)允許你為它取任意名字。
js:判斷為null或者undefined
if(a==null)//這個(gè)判斷即可
開(kāi)發(fā)時(shí)候遇到的問(wèn)題
在開(kāi)發(fā)detail的comment時(shí)候,遇到一個(gè)問(wèn)題:
組件結(jié)構(gòu)是 父 => 子: (page)Detail => (subpage)Comment => CommentList => Item
數(shù)據(jù)是在Comment組件中獲取的,fetch獲取到數(shù)據(jù)后,將數(shù)據(jù)傳遞給CommentList,再由CommentList傳遞給Item,由于Item中渲染的虛擬dom中一個(gè)參數(shù)寫(xiě)錯(cuò)了:
let data = this.props.data
<p>{item.comment}</p> {/*將data寫(xiě)成item了*/}
結(jié)果導(dǎo)致在Comment組件里,獲取到數(shù)據(jù)data后,this.setState({data})不成功,當(dāng)時(shí)是用console.log調(diào)試,在setState之前可以獲取到數(shù)據(jù),但是在setState之后console沒(méi)有任何反映,而且setState顯示也沒(méi)有生效,當(dāng)時(shí)一直以為是數(shù)據(jù)的問(wèn)題,調(diào)試了半天,數(shù)據(jù)肯定沒(méi)問(wèn)題,就在子組件找問(wèn)題,找到后改掉就正常了
debug調(diào)試發(fā)現(xiàn),才錯(cuò)誤參數(shù)渲染之前獲取到的數(shù)據(jù)都是正常的,此時(shí)setState還沒(méi)生效,而控制臺(tái)也不會(huì)報(bào)錯(cuò)
我的結(jié)論:setState后,由于state變化而導(dǎo)致的虛擬dom變化,虛擬dom因?yàn)閰?shù)錯(cuò)誤而無(wú)法渲染時(shí),setState就一直無(wú)法完成;這中錯(cuò)誤控制臺(tái)也不會(huì)報(bào)錯(cuò),通過(guò)再次測(cè)試,將Comment組件render里的一個(gè)參數(shù)故意寫(xiě)錯(cuò),確實(shí)還是一樣的狀況,且不會(huì)報(bào)錯(cuò),在開(kāi)發(fā)中要注意
this.setState()
http://www.tuicool.com/articles/zEfEfua
setState() 不會(huì)立刻改變 this.state ,而是創(chuàng)建一個(gè)即將處理的 state 轉(zhuǎn)變。在調(diào)用該方法之后訪問(wèn) this.state 可能會(huì)返回現(xiàn)有的值。
this.setState 是在 render 時(shí), state 才會(huì)改變調(diào)用的, 也就是說(shuō), setState 是異步的. 組件在還沒(méi)有渲染之前, this.setState 還沒(méi)有被調(diào)用.這么做的目的是為了提升性能, 在批量執(zhí)行 State 轉(zhuǎn)變時(shí)讓 DOM 渲染更快.
- setState是異步的
很多開(kāi)發(fā)剛開(kāi)始沒(méi)有注意到 setState 是異步的。如果你修改一些 state ,然后直接查看它,你會(huì)看到之前的 state 。這是 setState 中最容易出錯(cuò)的地方。 setState 這個(gè)詞看起來(lái)并不像是異步的,所以如果你不假思索的用它,可能會(huì)造成 bugs 。
另外, setState 函數(shù)還可以將一個(gè)回調(diào)函數(shù)作為參數(shù), 當(dāng) setState 執(zhí)行完并且組件重新渲染之后. 這個(gè)回調(diào)函數(shù)會(huì)執(zhí)行, 因此如果想查看通過(guò) setState 改變后的 state, 可以這樣寫(xiě):
this.setState({myState: nextState}, ()=>{console.log(this.state.myState)})
- setState會(huì)造成不必要的渲染
每次調(diào)用都會(huì)造成重新渲染。很多時(shí)候,這些重新渲染是不必要的。不必要的渲染有以下幾個(gè)原因:
- 新的 state 其實(shí)和之前的是一樣的。這個(gè)問(wèn)題通常可以通過(guò) shouldComponentUpdate 來(lái)解決。也可以用 pure render 或者其他的庫(kù)賴解決這個(gè)問(wèn)題。
- 通常發(fā)生改變的 state 是和渲染有關(guān)的,但是也有例外。比如,有些數(shù)據(jù)是根據(jù)某些狀態(tài)來(lái)顯示的。
- 有些 state 和渲染一點(diǎn)關(guān)系都沒(méi)有。有一些 state 可能是和事件、 timer ID 有關(guān)的。
所以:和渲染無(wú)關(guān)的狀態(tài)盡量不要放在 state 中來(lái)管理
通常 state 中只來(lái)管理和渲染有關(guān)的狀態(tài) ,從而保證 setState 改變的狀態(tài)都是和渲染有關(guān)的狀態(tài)。這樣子就可以避免不必要的重復(fù)渲染。其他和渲染無(wú)關(guān)的狀態(tài),可以直接以屬性的形式保存在組件中,在需要的時(shí)候調(diào)用和改變,不會(huì)造成渲染。
避免不必要的修改,當(dāng) state 的值沒(méi)有發(fā)生改變的時(shí)候,盡量不要使用 setState 。雖然 shouldComponentUpdate 和 PureComponent 可以避免不必要的重復(fù)渲染,但是還是增加了一層 shallowEqual 的調(diào)用,造成多余的浪費(fèi)。