React之表格操作
對於官網上的那個表格demo又進行了改造,記錄一下其中的困難和解決思路,當然還有功能沒有完善,會繼續利用空餘時間來實現一下。
——————————————————————————————————————————————————————
1.利用webpack進行了打包壓縮處理,並提取出了的第三方庫檔案;
2.增加了按照價格排序的功能;新加商品後也能按照排序的方式來顯示;
3.增加了分頁的功能;
4.減少了元件不必要的render;
——————————————————————————————————————————————————————
感悟:其實自己實現上面功能的過程也是比較“蛋疼的”,因為表格之間的狀態互動比較多,經常要各個子元件之間通訊,除了一些邏輯需要自己思考處理外,還有就是自己想實現一些不必要的render,這就需要用狀態來判斷,是否需要重新渲染元件,存在狀態難找,和流向不是那麼容易就容易理清楚的困難;隨著表格的功能越來越複雜,後面覺得有必要學習一下Redux了~
webpack
然後我再稍微說幾個需要注意的地方:
1.babel的loader中的名字要寫成'babel-loader'
2.生成 .babelrc 檔案需要寫成 .babelrc. 這樣的形式(前後各一個點)
3.如何設定NODE_ENV=production(React切換成產品模式 )
在cmd中直接敲set NODE_ENV=production
然後在webpack.config.js中
plugins: [ new webpack.DefinePlugin({ 'process.env': { NODE_ENV : JSON.stringify('production') } }), ]
4.將第三方庫另外打包一份(比如我們的react,react-dom這樣的第三方js,最好和我們自己定義的js分開,這樣webpack利用diff演算法打包的時候也會快一點,畢竟這些第三方庫我們基本不會去修改)
entry: { bundle:__dirname + "/app/main.js", vendor:["react","react-dom"] },
我們需要再entry中定義好,然後再plugins中利用webpakc自帶的外掛進行打包plugins: [ new webpack.optimize.UglifyJsPlugin({ output: { comments: false, // remove all comments }, compress: { warnings: false } }), new webpack.optimize.CommonsChunkPlugin({ name: 'vendor' }), new webpack.DefinePlugin({ 'process.env': { NODE_ENV: JSON.stringify('production') } }), ]
5.利用webpack-dev-server進行修改後實時重新整理自從每次打包後我這邊每改一次都要所有的重新打包,需耗費我10多秒的時間,真的是太....那個啥了;
不過在使用過程中我遇到了一個困難,就是本地執行成功了,但是無法實時重新整理;
我的解決方法如下:(都是針對我自己的demo進行修改,實際情況實際討論)
首先在package.json這個檔案中進行修改
"scripts": { "start": "webpack", "dev":"webpack-dev-server --inline --content-base build/" },
我們執行npm install start的時候是常規的打包,執行npm run dev是啟動webpack-dev-server,--content-base 後面接的是index.html檔案的路徑,我是放在build/下面的然後修改一下webpack.config.js
output: { path: __dirname + "/build/js", filename: "[name].js", publicPath: 'js/' },
最後修改一下index.html<script src="js/vendor.js"></script><script src="js/bundle.js"></script>
這樣就可以實現實時修改檔案,然後瀏覽器會自己重新整理了。最後我還是想說一下為什麼會出現這個原因,我個人的理解是,開啟webpack-dev-server後進行修改打包後生成的檔案沒有放在我的本地資料夾裡面(Path指定的路徑),而是在PublicPath指定的路徑;
path:用來存放打包後文件的輸出目錄 ;publicPath:指定資原始檔引用的目錄 ;
而我原來的index.html引用的js是用的Path的路徑,我也沒有指定PublicPath的路徑,所以它預設放在根路徑,這個根路徑就是http://localhost:8080/;
如果我不指定路徑,那麼index.html中可以就直接這樣xxx.js引用js;我這裡指定了publicPath : js/" 其實是http://localhost:8080/js,然後引用的時候寫成js/xx.js即可,這樣做是為了和我本地編譯打包的指令碼路徑一致,方便我本地的編譯,不用去再更改指令碼的引用路徑了;
6.如何配置多入口檔案
如果你是多頁面應用,那樣肯定就會有多個入口檔案,在我們的webpack.config.js中的entry需要一個個列出來
譬如:
entry:{a:__dirname + "/app/a.js",b:__dirname + "/app/b.js",c:__dirname + "/app/b.js",......}
這樣需要我們手動一個個去敲,肯定不方便,所以我們這邊要“智慧一點”
我的解決方法如下:
定義一個函式:
function getEntry(){ var entry = { bundle:__dirname + "/app/main.js", vendor:["react","react-dom"],//將第三方的依賴放入一個模組包中,打包成一個模組,如果裡面的檔案還有依賴則依賴性最高的放最後 }; glob.sync(__dirname +'/app/settings/*.js') //讀取開發目錄,並進行路徑裁剪 .forEach(function(name) { var start = name.lastIndexOf('/')+1, end = name.length-3; //儲存各個元件的入口 var n = name.slice(start, end); entry[n] = name; }); return entry; };
除了main.js和第三方庫外,還有一些其他的js我寫在了settings目錄下,所以傳的是__dirname +'/app/settings/*.js' 這裡面的具體路徑自己更換,我的app目錄是和webapck.config.js在同一目錄下然後:
entry:getEntry(),
就可以把我們需要打包壓縮的js全部包含進去,當前我的例子只是拋磚引玉。分頁
對於分頁,這裡我花了比較多的時間:
以前做過jq的分頁,知道了大體的思路,首先是需要知道一共有多少個數據,然後是每頁顯示多少行,然後就是求出一個頁數,再對錶格中的資料進行選擇性的顯示。
但是剛接觸到React分頁的時候還是一頭霧水,有點不知道從哪下手,畢竟React不能那麼容易就能操作到每一個節點,輕鬆獲取我們想要的資料。
我這邊的思路首先是從顯示和隱藏每一行開始,因為每一行我這邊封裝成了一個小元件,我這邊完全可以控制其display屬性來顯示,不過這裡需要注意的就是不能用jq的那種方法,addClass,removeClass或者是.css來控制,我們需要用變數來控制,比如:
var display="none" <tr style={{display:display}} />
這裡通過改變display變數來控制顯示。
當我們能夠去顯示和隱藏每一行後,就需要來進行選擇性顯示了和隱藏了;
if(this.props.index<this.props.currentPage*this.props.eachPage||this.props.index>(this.props.currentPage+1)*this.props.eachPage-1){ display="none"; }
this.props.index是當前這一行的索引,即資料當前行數;
this.props.currentPage是當前頁數;
this.props.eachPage是每一頁顯示行數;
比如,我們要顯示第一頁的資料,每一頁顯示5個數據,那麼顯示的具體行數就是0--4,那麼小於0的和大於4的我們就會去隱藏。
這裡的this.props.currentPage其實是個變數,是和另一個頁面跳轉的元件進行通訊的,中間就需要用父元件當作一個媒介來傳遞。當我點選下一頁,或者選擇具體的頁數進行跳轉的時候,需要改變這個資料。
當然我們還需要進行一些邊界問題的處理:
A.比如我們目前是第一頁,那麼我們的首頁和上一頁就要被灰掉,不能進行點選觸發,我們處於最後一頁的時候需要把下一頁和尾頁同樣灰掉;
我的做法如下:
類似於前面的隱藏和顯示,我這裡給首頁,前一頁,下一頁,尾頁都添加了一個變數"ban"為它的className(ban設定的CSS是灰掉這個按鈕);
然後我們就進行判斷,當前是首頁還是尾頁,相應的對這個ban變數進行賦值,如果不滿足條件就賦值一個空值,滿足就賦值我們定義好的css變數;
到這裡我們只是對css進行了灰掉,但是你點選的時候還是會觸發函式,所以我這裡又在點選的時候又進行了一次判斷;由於我這裡是利用的事件代理,所以對於這4個按鈕我只是定義了一個click函式,然後判斷他們的id,來進行判斷到底是哪個按鈕,在這個基礎上我再判斷是否className為ban ,如果是,則不會去觸發。
B.如果我現在處於最後一頁,我需要去刪除掉最後一個數據,那麼表格應該會自動跳轉到前一頁。
(這個問題對我來說也是具有挑戰性的)
我的思路如下:
首先判斷當前是不是最後一頁(如果不是,你刪除資料,表格會自動更新,後面的資料會補上)
如果是最後一頁,再判斷是不是最後一個數據(這裡的最後一個數據是所有資料的最後一個,那樣這樣刪除跳轉就沒有意義,而且還會產生bug)
如果不是最後一個數據,再判斷是不是最後一頁的最後一個數據(如果不是,後面會資料會補齊)
如果是,就更新當前的頁數為上一頁
ps:這裡判斷總頁數的方法可以參考一下,我這裡是用總商品除以每頁顯示的行數,pasreInt是去除小數部分,如果正好可以整除,那麼除出來的資料正好可以當作頁數,如果不能整除的話,那麼就向上取捨。(具體情況還是具體分析吧,我這裡的總頁數是1,2,3但是我當前頁是0,1,2)
if(this.state.products.length/this.state.eachPage==parseInt(this.state.products.length/this.state.eachPage)) { var page=parseInt(this.state.products.length/this.state.eachPage); } else { var page=Math.ceil(this.state.products.length/this.state.eachPage); } if(this.state.currentPage==page&&this.state.products.length!=0&&this.state.products.length%this.state.eachPage==0) { this.setState({ products:this.state.products, currentPage:this.state.currentPage-1 }) } else{ this.setState({ products:this.state.products, }) }
最後還需要注意的就是,我這裡跳轉具體頁數是select選項和前一頁它們按鈕觸發方式不一樣,前者是onChange後者是onClick
減少render
其實元件更新的時候,很多相關元件也會同時更新渲染,有些元件是死的,有些是沒必要更新的,所以這裡我們可以選擇性進行更新。
這裡就是當它的isAdd屬性改變了我就進行更新,這個就是我們的新增商品的視窗,只有我點選添加了再進行元件渲染,其他時候沒必要進行元件渲染。
shouldComponentUpdate(nextProps, nextState) { if (this.props.isAdd !== nextProps.isAdd) { return true; } return false;}