前面咱們對微信小程序進行了研究:【微信小程序項目實踐總結】30分鐘從陌生到熟悉javascript
而且用小程序翻寫了以前一個demo:【組件化開發】前端進階篇之如何編寫可維護可升級的代碼css
以前一直在跟業務方打交道後面研究了下後端,期間還作了一些運營、管理相關工做,哈哈,最近一年工做經歷十分豐富啊,生命在於不斷的嘗試嘛。html
固然,不可避免的在前端技術一塊也稍微有點落後,對React&Vue沒有進行過深刻一點的研究,這裏得空咱們便來一塊兒研究一番(回想起來寫代碼的日子纔是最快樂的😆),由於咱們如今也慢慢在切React、想嘗試下React Native,可是我這邊對於到底React仍是Vue仍是比較疑惑,因此我這裏研究下,順便看看能不能解決小程序一套代碼的問題前端
咱們如今就來嘗試,是否能夠用React或者Vue讓一套代碼能在三端同時運行vue
咱們這裏依舊使用這個我以爲還算複雜的例子,作一個首頁一個列表頁,後面嘗試將這套代碼翻譯成React Native以及微信小程序,因而便開始咱們此次的學習吧java
PS:我這裏對React&Vue熟悉度通常,文中就是demo研究,有不妥的地方請各位指正react
工欲善其事必先利其器,咱們這裏依舊先作UI組件,首先咱們作一個彈出層提示組件alert,這裏咱們儘可能嘗試與小程序開發模式保持一致點npm
咱們這裏先來建立一個組件:小程序
1 class UIAlert extends React.Component { 2 propType() { 3 //name必須有,而且必須是字符串 4 name: React.PropTypes.string.isRequired 5 } 6 render() { 7 return ( 8 <view>我是{this.props.name}</view> 9 ); 10 } 11 }; 12 13 React.render( 14 <UIAlert name="alert"/>, 15 document.getElementById('main') 16 );
//輸出 我是alert
生成的HTML結構爲:後端
1 <view data-reactid=".0"> 2 <span data-reactid=".0.0">我是</span><span data-reactid=".0.1">alert</span> 3 </view>
這裏view顯然不會被識別,咱們簡單作下處理(這裏順便升級下咱們React的版本):
1 class View extends React.Component { 2 render() { 3 return ( 4 <div >{this.props.children}</div> 5 ); 6 } 7 } 8 class UIAlert extends React.Component { 9 render() { 10 return ( 11 <View>我是{this.props.name}</View> 12 ); 13 } 14 }; 15 ReactDOM.render( 16 <UIAlert name="alert" />, 17 document.getElementById('root') 18 );
因而咱們生成了這個樣子的代碼,沒有額外添加span也沒有添加id標識了:
<div id="root"><div>我是alert</div></div>
咱們這裏依舊以一個實際的例子來講明React的各類細節,這裏咱們索性來作一個提示框組件吧,這裏咱們先實現一個遮蓋層:
1 class UIMask extends React.Component { 2 constructor(props) { 3 super(props); 4 this.state = { 5 }; 6 this.onClick = this.onClick.bind(this); 7 } 8 onClick(e) { 9 if(e.target.dataset.flag !== 'mask') return; 10 this.props.onMaskClick(); 11 } 12 render() { 13 return ( 14 <div onClick={this.onClick} data-flag="mask" className="cm-overlay" style={{zIndex: this.props.uiIndex, display: this.props.isShow}} > 15 {this.props.children} 16 </div> 17 ); 18 } 19 }
這裏簡單說下React中狀態以及屬性的區別(我理解下的區別):
React中的屬性是隻讀的,原則上部容許修改本身的屬性,他通常做爲屬性由父組件傳入
state做爲組件狀態機而存在,表示組件處於不一樣的狀態,因此是可變的,state是組件數據基礎
這句話說的好像比較抽象,這裏具體表達一下是:
① 屬性能夠從父組件獲取,而且父組件賦值是組件的主要使用方式
② 一個組件內部不會有調用setProps相似的方法指望引發屬性的變化
③ 總之屬性即是組件的固有屬性,咱們只能像函數同樣使用而不是想去改變
④ 若是你想改變一個屬性的值,那麼說明他該被定義爲狀態
⑤ 反之若是一個變量能夠從父組件中獲取,那麼他必定不是一個狀態
這裏以咱們這裏的遮蓋層組件爲例說明:
遮蓋層的z-index以及是否顯示,對於遮蓋層來講就是最小原子單元了,並且他們也是父組件經過屬性的方式傳入的,咱們看看這裏提示框的代碼:
1 class UIAlert extends React.Component { 2 constructor(props) { 3 super(props); 4 this.state = { 5 isShow: '', 6 uiIndex: 3000, 7 title: '', 8 message: 'message', 9 btns: [{ 10 type: 'ok', 11 name: '肯定' 12 }, { 13 type: 'cancel', 14 name: '取消' 15 }] 16 }; 17 this.onMaskClick = this.onMaskClick.bind(this); 18 19 } 20 21 onMaskClick() { 22 this.setState({ 23 isShow: 'none' 24 }); 25 } 26 27 render() { 28 29 return ( 30 <UIMask onMaskClick={this.onMaskClick} uiIndex={this.state.uiIndex} isShow={this.state.isShow}> 31 <div className="cm-modal cm-modal--alert" style={{zIndex: this.state.uiIndex + 1, display: this.state.isShow}}> 32 <div className="cm-modal-bd"> 33 <div className="cm-alert-title">{this.state.title}</div> 34 {this.state.message.length > 10 ? <div className="cm-mutil-lines">{this.state.message}</div> : <div>{this.state.message}</div>} 35 </div> 36 <div className={this.state.btns.length > 2 ? 'cm-actions cm-actions--full' : 'cm-actions '}> 37 { 38 this.state.btns.map(function(item) { 39 return <span data-type={item.type} className={item.type == 'ok' ? 'cm-btns-ok cm-actions-btn ' : 'cm-actions-btn cm-btns-cancel'}>{item.name}</span> 40 }) 41 } 42 </div> 43 </div> 44 </UIMask> 45 ); 46 } 47 };
爲了方便調試,咱們這裏給提示框組件定義了不少「狀態」,而這裏面的狀態可能有不少是「不合適」的,能夠看到咱們遮蓋層UIMask使用的幾個屬性所有是這裏傳入的,而後咱們這裏想象下真實使用場景,確定是全局有一個變量,或者按鈕控制顯示隱藏,因此咱們這個組件進行一輪改造:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <link href="./static/css/global.css" rel="stylesheet" type="text/css"/> 7 <script src="./libs/react.development.js"></script> 8 <script src="./libs/react-dom.development.js"></script> 9 <script src="./libs/JSXTransformer.js"></script> 10 </head> 11 <body> 12 13 <div id="root">ddd</div> 14 15 16 <script type="text/jsx"> 17 18 19 20 class UIMask extends React.Component { 21 constructor(props) { 22 super(props); 23 this.state = { 24 }; 25 this.onClick = this.onClick.bind(this); 26 } 27 onClick(e) { 28 if(e.target.dataset.flag !== 'mask') return; 29 this.props.onMaskClick(); 30 } 31 render() { 32 return ( 33 <div onClick={this.onClick} data-flag="mask" className="cm-overlay" style={{zIndex: this.props.uiIndex, display: this.props.isShow}} > 34 {this.props.children} 35 </div> 36 ); 37 } 38 } 39 40 class UIAlert extends React.Component { 41 constructor(props) { 42 super(props); 43 this.state = { 44 uiIndex: 3000 45 }; 46 this.onMaskClick = this.onMaskClick.bind(this); 47 this.onBtnClick = this.onBtnClick.bind(this); 48 49 } 50 onMaskClick() { 51 this.props.hideMessage(); 52 } 53 onBtnClick(e) { 54 let index = e.target.dataset.index; 55 this.props.btns[index].callback(); 56 } 57 render() { 58 let scope = this; 59 return ( 60 <UIMask onMaskClick={this.onMaskClick} uiIndex={this.state.uiIndex} isShow={this.props.isShow}> 61 <div className="cm-modal cm-modal--alert" style={{zIndex: this.state.uiIndex + 1, display: this.props.isShow}}> 62 <div className="cm-modal-bd"> 63 <div className="cm-alert-title">{this.props.title}</div> 64 {this.props.message.length > 10 ? <div className="cm-mutil-lines">{this.props.message}</div> : <div>{this.props.message}</div>} 65 </div> 66 <div className={this.props.btns.length > 2 ? 'cm-actions cm-actions--full' : 'cm-actions '}> 67 { 68 this.props.btns.map(function(item, index) { 69 return <span onClick={scope.onBtnClick} data-index={index} data-type={item.type} className={item.type == 'ok' ? 'cm-btns-ok cm-actions-btn ' : 'cm-actions-btn cm-btns-cancel'}>{item.name}</span> 70 }) 71 } 72 </div> 73 </div> 74 </UIMask> 75 ); 76 } 77 }; 78 79 80 81 82 //這裏是真正的調用者,頁面級別控制器 83 84 class MainPage extends React.Component { 85 constructor(props) { 86 super(props); 87 this.showMessage = this.showMessage.bind(this); 88 this.hideMessage = this.hideMessage.bind(this); 89 let scope = this; 90 91 this.state = { 92 alertShow: 'none', 93 btns: [{ 94 type: 'ok', 95 name: '肯定', 96 callback: function() { 97 scope.hideMessage(); 98 console.log('成功'); 99 } 100 }, { 101 type: 'cancel', 102 name: '取消', 103 callback: function() { 104 scope.hideMessage(); 105 console.log('取消'); 106 } 107 }] 108 }; 109 110 } 111 showMessage() { 112 this.setState({ 113 alertShow: '' 114 }) 115 } 116 hideMessage() { 117 this.setState({ 118 alertShow: 'none' 119 }) 120 } 121 render() { 122 return ( 123 <div> 124 <input type="button" value="我是一個通常的按鈕" onClick={this.showMessage} /> 125 <UIAlert 126 title="title" 127 message="點點滴滴" 128 btns={this.state.btns} 129 showMessage={this.showMessage} 130 hideMessage={this.hideMessage} 131 isShow={this.state.alertShow} 132 name="alert" 133 uiIndex="333" 134 /> 135 </div> 136 ); 137 } 138 } 139 140 141 ReactDOM.render( 142 <MainPage />, 143 document.getElementById('root') 144 ); 145 146 </script> 147 148 </body> 149 </html>
如此一來,咱們這個組件便基本結束了,能夠看到,事實上咱們頁面組件全部的狀態所有聚集到了頂層組件也就是頁面層級來了,在頁面層級依舊須要分塊處理,不然代碼依舊可能會很亂
階段總結
咱們這裏回顧小程序中的彈出層組件是這樣寫的:
1 <view class="cm-modal cm-modal--alert" style="z-index: {{uiIndex}}; display: {{isShow}}; "> 2 <view class="cm-modal-bd"> 3 <block wx:if="{{title}}"> 4 <view class="cm-alert-title">{{title}}</view> 5 </block> 6 <block wx:if="{{message.length > 20}}"> 7 <view class="cm-mutil-lines">{{message}}</view> 8 </block> 9 <block wx:else> 10 <view>{{message}}</view> 11 </block> 12 </view> 13 <view class="cm-actions {{ btns.length > 2 ? 'cm-actions--full' : '' }}"> 14 <block wx:for="{{btns}}" wx:key="{{k}}"> 15 <text bindtap="onBtnEvent" data-type="{{item.type}}" class="{{item.type == 'ok' ? 'cm-btns-ok' : 'cm-btns-cancel'}} cm-actions-btn">{{item.name}}</text> 16 </block> 17 18 </view> 19 </view> 20 <view class="cm-overlay" bindtap="onMaskEvent" style="z-index: {{maskzIndex}}; display: {{isShow}}"> 21 </view>
簡單研究到這裏,感受要使用React完成一套代碼三端運行有點困難,小程序中的模板所有不能直接調用就是,除非是有wxs,而React支持的就是模板中寫不少js,除非給React作不少的規則限制,或者由React編譯爲小程序識別的代碼,不然暫時是不大可能的,因此咱們如今研究下Vue是否是合適
這裏先不考慮mpvue框架,不然一點神祕感都沒有了,咱們依舊使用彈出層組件爲例,用純純的Vue代碼嘗試是否作獲得,首先Vue會將代碼與模板作一個綁定,大概能夠這樣:
1 <div id="app"> 2 <p>{{ foo }}</p> 3 </div> 4 <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> 5 <script type="text/javascript"> 6 var obj = { 7 foo: 'bar' 8 } 9 new Vue({ 10 el: '#app', 11 data: obj 12 }) 13 </script>
這塊與小程序還比較相似,因而咱們來完成咱們的提示框組件:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <link href="./static/css/global.css" rel="stylesheet" type="text/css"/> 7 </head> 8 <body> 9 10 <div id="app"> 11 <input type="button" value="我是一個通常的按鈕" v-on:click="showMessage" /> 12 <ui-mask v-bind:style="{zIndex: uiIndex, display: isShow}"> 13 <ui-alert :title="title" 14 :message="message" 15 v-bind:style="{zIndex: uiIndex + 1, display: isShow}" 16 :btns="btns" ></ui-alert> 17 </ui-mask> 18 </div> 19 20 21 <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> 22 <script type="text/javascript"> 23 24 Vue.component('ui-mask', { 25 methods:{ 26 onClick: function (e) { 27 if(e.target.dataset.flag !== 'mask') return; 28 this.$parent.hideMessage(); 29 } 30 }, 31 template: ` 32 <div v-on:click="onClick" data-flag="mask" class="cm-overlay" > 33 <slot></slot> 34 </div> 35 ` 36 }); 37 38 Vue.component('ui-alert', { 39 props: { 40 title: { 41 type: String 42 }, 43 message: { 44 type: String 45 }, 46 uiIndex: { 47 type: String 48 }, 49 isShow: { 50 type: String 51 }, 52 btns: Array 53 }, 54 methods: { 55 onBtnEvent: function (e) { 56 let index = e.target.dataset.index; 57 this.btns[index].callback.call(this.$parent.$parent); 58 } 59 }, 60 template: ` 61 <div class="cm-modal cm-modal--alert"> 62 <div class="cm-modal-bd"> 63 <template v-if="title"> 64 <div class="cm-alert-title">{{title}}</div> 65 </template> 66 <template v-if="message.length > 20"> 67 <div class="cm-mutil-lines">{{message}}</div> 68 </template> 69 <template v-else> 70 <div>{{message}}</div> 71 </template> 72 </div> 73 <div class="cm-actions" v-bind:class="[btns.length > 2 ? 'cm-actions--full' : '']"> 74 <template v-for="(item, index) in btns"> 75 <span v-on:click="onBtnEvent" :data-index="index" class="cm-actions-btn" v-bind:class="[item.type == 'ok' ? 'cm-btns-ok' : 'cm-btns-cancel']">{{item.name}}</span> 76 </template> 77 </div> 78 </div> 79 ` 80 }) 81 new Vue({ 82 methods: { 83 showMessage: function() { 84 this.isShow = ''; 85 }, 86 hideMessage: function() { 87 this.isShow = 'none'; 88 } 89 }, 90 data: function() { 91 return { 92 title: 'title1', 93 message: 'message1', 94 uiIndex: 3000, 95 isShow: 'none', 96 btns: [{ 97 type: 'ok', 98 name: '肯定', 99 callback: function() { 100 this.hideMessage(); 101 console.log('成功'); 102 } 103 }, { 104 type: 'cancel', 105 name: '取消', 106 callback: function() { 107 this.hideMessage(); 108 console.log('取消'); 109 } 110 }] 111 }; 112 } , 113 el: '#app' 114 }) 115 116 </script> 117 </body> 118 </html>
從類似度上來講比React高得多,可是仍然有不少區別:
① class&style一塊的處理
② 屬性的傳遞
③ setData....
這些等咱們後續代碼寫完看看如何處理之,能不能達到一套代碼三端運行,這裏作首頁的簡單實現。
這裏簡單的組織了下目錄結構,作了最簡單的實現,這裏你們注意我這裏對Vue不是很熟悉,不會遵循Vue的最佳實踐,我這裏是在探索能不能借助Vue讓一套代碼三端運行,因此這裏寫法會盡可能靠近小程序寫法:
1 <html lang="en"> 2 <head> 3 <meta charset="UTF-8"> 4 <title>Title</title> 5 <link href="./static/css/global.css" rel="stylesheet" type="text/css"/> 6 <link href="./static/css/index.css" rel="stylesheet" type="text/css"/> 7 <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> 8 </head> 9 <body> 10 <div id="app"></div> 11 <script type="module"> 12 import index from './pages/index/index.js' 13 Vue.component(index.name, index.data); 14 new Vue({ 15 data: function() { 16 return { 17 }; 18 } , 19 template: `<page-index></page-index>`, 20 el: '#app' 21 }) 22 </script> 23 </body> 24 </html>
1 import html from './index.html.js' 2 3 export default { 4 name: 'page-index', 5 data: { 6 template: html, 7 methods: { 8 showCitylist: function(e) { 9 console.log('showCitylist') 10 }, 11 showCalendar: function(e) { 12 console.log('showCalendar') 13 }, 14 goList: function(e) { 15 console.log('goList') 16 } 17 }, 18 data: function() { 19 return { 20 cityStartName: '請選擇出發地', 21 cityArriveName: '請選擇到達地', 22 calendarSelectedDateStr: '請選擇出發日期'} 23 } 24 } 25 }
1 export default 2 `<div class="container"> 3 <div class="c-row search-line" data-flag="start" @click="showCitylist"> 4 <div class="c-span3"> 5 出發</div> 6 <div class="c-span9 js-start search-line-txt"> 7 {{cityStartName}}</div> 8 </div> 9 <div class="c-row search-line" data-flag="arrive" @click="showCitylist"> 10 <div class="c-span3"> 11 到達</div> 12 <div class="c-span9 js-arrive search-line-txt"> 13 {{cityArriveName}}</div> 14 </div> 15 <div class="c-row search-line" data-flag="arrive" @click="showCalendar"> 16 <div class="c-span3"> 17 出發日期</div> 18 <div class="c-span9 js-arrive search-line-txt"> 19 {{calendarSelectedDateStr}}</div> 20 </div> 21 <div class="c-row " data-flag="arrive"> 22 <span class="btn-primary full-width js_search_list" @click="goList" >查詢</span> 23 </div> 24 </div> 25 `
如此咱們首頁頁面框架就出來了,後續只須要完成對應的幾個組件,如日曆組件以及城市列表,這裏爲了更完整的體驗,咱們先完成日曆組件
以前咱們作日曆組件的時候作的比較複雜,這裏能夠看到小程序中裏面的模板代碼:
1 <wxs module="dateUtil"> 2 var isDate = function(date) { 3 return date && date.getMonth; 4 }; 5 6 var isLeapYear = function(year) { 7 //傳入爲時間格式須要處理 8 if (isDate(year)) year = year.getFullYear() 9 if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) return true; 10 return false; 11 }; 12 13 var getDaysOfMonth = function(date) { 14 var month = date.getMonth(); //注意此處月份要加1,因此咱們要減一 15 var year = date.getFullYear(); 16 return [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; 17 } 18 19 var getBeginDayOfMouth = function(date) { 20 var month = date.getMonth(); 21 var year = date.getFullYear(); 22 var d = getDate(year, month, 1); 23 return d.getDay(); 24 } 25 26 var getDisplayInfo = function(date) { 27 28 if (!isDate(date)) { 29 date = getDate(date) 30 } 31 var year = date.getFullYear(); 32 33 var month = date.getMonth(); 34 var d = getDate(year, month); 35 36 37 //這個月一共多少天 38 var days = getDaysOfMonth(d); 39 40 //這個月是星期幾開始的 41 var beginWeek = getBeginDayOfMouth(d); 42 43 return { 44 year: year, 45 month: month, 46 days: days, 47 beginWeek: beginWeek 48 } 49 }; 50 51 //分割月之間的展現 52 var monthClapFn = function(year, month) { 53 month = month + 1; 54 return year + '年' + (month) + '月'; 55 }; 56 57 var getChangedDate = function(date, m) { 58 59 if (!isDate(date)) { 60 date = getDate(date) 61 } 62 var year = date.getFullYear(); 63 64 var month = date.getMonth(); 65 var changedMonth = month + m; 66 var yyy = parseInt((month + m) / 12); 67 if (changedMonth > 11) { 68 changedMonth = changedMonth - 12 * yyy; 69 } 70 changedYear = year + yyy; 71 72 return { 73 str_month: monthClapFn(changedYear, changedMonth) 74 date: getDate(changedYear, changedMonth), 75 year: changedYear, 76 month: changedMonth 77 }; 78 }; 79 80 var isSelected = function(date, year, month, day) { 81 if (!isDate(date)) { 82 date = getDate(date); 83 } 84 85 if (date.getFullYear() == year && date.getMonth() == month && date.getDate() == day) return 'active'; 86 return ''; 87 88 }; 89 90 var formatNum = function(n) { 91 if (n < 10) return '0' + n; 92 return n; 93 }; 94 95 var getDayName = function(dayMap, month, day) { 96 97 if (!dayMap) { 98 dayMap = { 99 '0101': '元旦節', 100 '0214': '情人節', 101 '0501': '勞動節', 102 '0601': '兒童節', 103 '0910': '教師節', 104 '1001': '國慶節', 105 '1225': '聖誕節' 106 }; 107 } 108 109 var name = formatNum(parseInt(month) + 1) + formatNum(day); 110 111 return dayMap[name] || day; 112 }; 113 114 module.exports = { 115 test: function (zzz) { 116 console.log('test', zzz) 117 }, 118 getDipalyInfo: getDisplayInfo, 119 getChangedDate: getChangedDate, 120 isSelected: isSelected, 121 getDayName: getDayName 122 } 123 </wxs> 124 <view class="cm-calendar" style="display: {{isShow}};"> 125 <view class="cm-calendar-hd "> 126 <block wx:for="{{weekDayArr}}" wx:key="weekDayKey"> 127 <view class="item">{{item}}</view> 128 </block>i 129 </view> 130 131 <block wx:for="{{displayMonthNum}}" wx:for-index="i" wx:key="t"> 132 133 <view class="cm-calendar-bd "> 134 <view class="cm-month ex-class"> 135 {{dateUtil.getChangedDate(displayTime, i).str_month }} 136 </view> 137 <view class="cm-day-list"> 138 139 <block wx:key="tt" wx:for="{{dateUtil.getDipalyInfo(dateUtil.getChangedDate(displayTime, i).date).days + dateUtil.getDipalyInfo(dateUtil.getChangedDate(displayTime, i).date).beginWeek}}" wx:for-index="index"> 140 141 <view wx:if="{{index < dateUtil.getDipalyInfo(dateUtil.getChangedDate(displayTime, i).date).beginWeek }}" class="item "></view> 142 <view bindtap="onDayTap" wx:else data-year="{{dateUtil.getChangedDate(displayTime, i).year}}" data-month="{{dateUtil.getChangedDate(displayTime, i).month}}" data-day="{{index + 1 - dateUtil.getDipalyInfo(dateUtil.getChangedDate(displayTime, i).date).beginWeek}}" class="item {{dateUtil.isSelected(selectedDate, dateUtil.getChangedDate(displayTime, i).year, dateUtil.getChangedDate(displayTime, i).month, index + 1 - dateUtil.getDipalyInfo(dateUtil.getChangedDate(displayTime, i).date).beginWeek)}}"> 143 <view class="cm-field-title"> 144 {{dateUtil.getDayName(dayMap, dateUtil.getChangedDate(displayTime, i).month, index + 1 - dateUtil.getDipalyInfo(dateUtil.getChangedDate(displayTime, i).date).beginWeek) }} {{}} 145 </view> 146 </view> 147 148 </block> 149 150 <view class=" cm-item--disabled " data-cndate="" data-date=""> 151 </view> 152 153 </view> 154 </view> 155 156 </block> 157 158 </view>
我思考了下,應該仍是儘可能少在模板裏面調用js方法,因此咱們這裏將粒度再拆細一點,實現一個單日的表格組件,因此咱們這裏的日曆由兩部分組成:
① 日曆-月組件
② 日曆-天組件,被月所包裹
因此咱們這裏先來實現一個天的組件,對於日從來說,他會擁有如下特性:
① 是否過時,這個會影響顯示效果
② 是否特殊節日,如中秋等,也會影響顯示
③ 點擊事件響應......
④ 是否是今天
好像也沒多少東西...
這裏先簡單些個demo將日曆單元格展現出來:
<ui-calendar year="2018" month="8" day="8" ></ui-calendar>
1 import data from './ui-calendar-day.js' 2 3 Vue.component(data.name, data.data); 4 5 export default { 6 name: 'ui-calendar', 7 data: { 8 methods: { 9 }, 10 data: function() { 11 return { 12 } 13 }, 14 template: 15 ` 16 <ul> 17 <template v-for="num in 30" > 18 <ui-calendar-day year="2018" month="8" v-bind:day="num" ></ui-calendar-day> 19 </template> 20 </ul> 21 ` 22 } 23 }
1 //公共方法抽離,輸入年月日判斷在今天前仍是今天后 2 function isOverdue(year, month, day) { 3 let date = new Date(year, month, day); 4 let now = new Date(); 5 now = new Date(now.getFullYear(), now.getMonth(), now.getDate()); 6 return date.getTime() < now.getTime(); 7 } 8 9 function isToday(year, month, day) { 10 let date = new Date(year, month, day); 11 let now = new Date(); 12 now = new Date(now.getFullYear(), now.getMonth(), now.getDate()); 13 return date.getTime() === now.getTime(); 14 } 15 16 export default { 17 name: 'ui-calendar-day', 18 data: { 19 props: { 20 year: { 21 type: String 22 }, 23 month: { 24 type: String 25 }, 26 day: { 27 type: Number 28 } 29 }, 30 methods: { 31 }, 32 data: function() { 33 //是否過時了 34 let klass = isOverdue(this.year, this.month, this.day) ? 'cm-item--disabled' : ''; 35 if(isToday(this.year, this.month, this.day)) klass += 'active' 36 37 return { 38 klass: klass 39 } 40 }, 41 template: 42 ` 43 <li v-bind:class="klass" v-bind:data-year="year" v-bind:data-month="month" v-bind:data-day="day"> 44 {{day}} 45 </li> 46 ` 47 } 48 }
造成的html結構以下:
1 <ul year="2018" month="8" day="8"><li data-year="2018" data-month="8" data-day="1" class="cm-item--disabled"> 2 1 3 </li><li data-year="2018" data-month="8" data-day="2" class="cm-item--disabled"> 4 2 5 </li><li data-year="2018" data-month="8" data-day="3" class="cm-item--disabled"> 6 3 7 </li><li data-year="2018" data-month="8" data-day="4" class="cm-item--disabled"> 8 4 9 </li><li data-year="2018" data-month="8" data-day="5" class="cm-item--disabled"> 10 5 11 </li><li data-year="2018" data-month="8" data-day="6" class="cm-item--disabled"> 12 6 13 </li><li data-year="2018" data-month="8" data-day="7" class="cm-item--disabled"> 14 7 15 </li><li data-year="2018" data-month="8" data-day="8" class="active"> 16 8 17 </li><li data-year="2018" data-month="8" data-day="9" class=""> 18 9 19 </li><li data-year="2018" data-month="8" data-day="10" class=""> 20 10 21 </li><li data-year="2018" data-month="8" data-day="11" class=""> 22 11 23 </li><li data-year="2018" data-month="8" data-day="12" class=""> 24 12 25 </li><li data-year="2018" data-month="8" data-day="13" class=""> 26 13 27 </li><li data-year="2018" data-month="8" data-day="14" class=""> 28 14 29 </li><li data-year="2018" data-month="8" data-day="15" class=""> 30 15 31 </li><li data-year="2018" data-month="8" data-day="16" class=""> 32 16 33 </li><li data-year="2018" data-month="8" data-day="17" class=""> 34 17 35 </li><li data-year="2018" data-month="8" data-day="18" class=""> 36 18 37 </li><li data-year="2018" data-month="8" data-day="19" class=""> 38 19 39 </li><li data-year="2018" data-month="8" data-day="20" class=""> 40 20 41 </li><li data-year="2018" data-month="8" data-day="21" class=""> 42 21 43 </li><li data-year="2018" data-month="8" data-day="22" class=""> 44 22 45 </li><li data-year="2018" data-month="8" data-day="23" class=""> 46 23 47 </li><li data-year="2018" data-month="8" data-day="24" class=""> 48 24 49 </li><li data-year="2018" data-month="8" data-day="25" class=""> 50 25 51 </li><li data-year="2018" data-month="8" data-day="26" class=""> 52 26 53 </li><li data-year="2018" data-month="8" data-day="27" class=""> 54 27 55 </li><li data-year="2018" data-month="8" data-day="28" class=""> 56 28 57 </li><li data-year="2018" data-month="8" data-day="29" class=""> 58 29 59 </li><li data-year="2018" data-month="8" data-day="30" class=""> 60 30 61 </li></ul>
簡單來講,關於天的處理彷佛處理完成,咱們如今來開始月的處理,因而日曆雛形就已經出來了:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <link href="./static/css/global.css" rel="stylesheet" type="text/css"/> 7 <link href="./static/css/index.css" rel="stylesheet" type="text/css"/> 8 <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> 9 </head> 10 <body> 11 <div id="app"></div> 12 <script type="module"> 13 import index from './pages/index/index.js' 14 import data from './components/ui-calendar.js' 15 Vue.component(index.name, index.data); 16 Vue.component(data.name, data.data); 17 new Vue({ 18 data: function() { 19 return { 20 }; 21 } , 22 // template: `<page-index></page-index>`, 23 template: `<ui-calendar year="2018" month="8" day="8" ></ui-calendar>`, 24 el: '#app' 25 }) 26 </script> 27 </body> 28 </html>
1 import data from './ui-calendar-month.js' 2 3 Vue.component(data.name, data.data); 4 5 export default { 6 name: 'ui-calendar', 7 data: { 8 methods: { 9 }, 10 data: function() { 11 return { 12 weekDayArr: ['日', '一', '二', '三', '四', '五', '六'] 13 } 14 }, 15 template: 16 ` 17 <ul class="cm-calendar "> 18 <ul class="cm-calendar-hd"> 19 <template v-for="i in 7" > 20 <li class="cm-item--disabled">{{weekDayArr[i-1]}}</li> 21 </template> 22 </ul> 23 <ui-calendar-month year="2018" month="8" ></ui-calendar-month> 24 </ul> 25 ` 26 } 27 }
1 import data from './ui-calendar-day.js' 2 3 Vue.component(data.name, data.data); 4 5 //公共方法抽離,輸入年月日判斷在今天前仍是今天后 6 function isLeapYear(year) { 7 if ((typeof year == 'object') && (year instanceof Date)) year = year.getFullYear() 8 if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) return true; 9 return false; 10 } 11 12 // @description 獲取一個月份1號是星期幾,注意此時保留開發習慣,月份傳入時須要自主減一 13 // @param year {num} 多是年份或者爲一個date時間 14 // @param year {num} 月份 15 // @return {num} 當月一號爲星期幾0-6 16 function getBeginDayOfMouth(year, month) { 17 //自動減一以便操做 18 month--; 19 if ((typeof year == 'object') && (year instanceof Date)) { 20 month = year.getMonth(); 21 year = year.getFullYear(); 22 } 23 var d = new Date(year, month, 1); 24 return d.getDay(); 25 } 26 27 export default { 28 name: 'ui-calendar-month', 29 data: { 30 props: { 31 year: { 32 type: String 33 }, 34 month: { 35 type: String 36 } 37 }, 38 methods: { 39 }, 40 data: function() { 41 let days = [31, isLeapYear(this.year) ? 29 : 28, 42 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; 43 //本月從哪天開始 44 let beforeDays = getBeginDayOfMouth(this.year, parseInt(this.month) + 1); 45 46 return { 47 days: days[this.month], 48 beforeDays: beforeDays 49 } 50 }, 51 template: 52 ` 53 <ul class="cm-calendar-bd "> 54 <h3 class="cm-month js_month">{{year + '-' + month}}</h3> 55 <ul class="cm-day-list"> 56 <template v-for="n in beforeDays" > 57 <li class="cm-item--disabled"></li> 58 </template> 59 <template v-for="num in days" > 60 <ui-calendar-day :year="year" :month="month" v-bind:day="num" ></ui-calendar-day> 61 </template> 62 </ul> 63 </ul> 64 ` 65 } 66 }
1 //公共方法抽離,輸入年月日判斷在今天前仍是今天后 2 function isOverdue(year, month, day) { 3 let date = new Date(year, month, day); 4 let now = new Date(); 5 now = new Date(now.getFullYear(), now.getMonth(), now.getDate()); 6 return date.getTime() < now.getTime(); 7 } 8 9 function isToday(year, month, day) { 10 let date = new Date(year, month, day); 11 let now = new Date(); 12 now = new Date(now.getFullYear(), now.getMonth(), now.getDate()); 13 return date.getTime() === now.getTime(); 14 } 15 16 export default { 17 name: 'ui-calendar-day', 18 data: { 19 props: { 20 year: { 21 type: String 22 }, 23 month: { 24 type: String 25 }, 26 day: { 27 type: Number 28 } 29 }, 30 methods: { 31 }, 32 data: function() { 33 //是否過時了 34 let klass = isOverdue(this.year, this.month, this.day) ? 'cm-item--disabled' : ''; 35 if(isToday(this.year, this.month, this.day)) klass += 'active' 36 return { 37 klass: klass 38 } 39 }, 40 template: 41 ` 42 <li v-bind:class="klass" v-bind:data-year="year" v-bind:data-month="month" v-bind:data-day="day"> 43 <div class="cm-field-wrapper "><div class="cm-field-title">{{day}}</div></div> 44 </li> 45 ` 46 } 47 }
這樣分解下來,彷佛代碼變得更加簡單了,接下來咱們花點功夫完善這個組件,最後造成了這樣的代碼:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <link href="./static/css/global.css" rel="stylesheet" type="text/css"/> 7 <link href="./static/css/index.css" rel="stylesheet" type="text/css"/> 8 <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> 9 </head> 10 <body> 11 <div id="app"></div> 12 <script type="module"> 13 // console.error = function () { 14 // } 15 import index from './pages/index/index.js' 16 import data from './components/ui-calendar.js' 17 Vue.component(index.name, index.data); 18 Vue.component(data.name, data.data); 19 new Vue({ 20 methods: { 21 onDayClick: function (data) { 22 this.selectedDate = new Date(data.year, data.month, data.day).getTime() + ''; 23 // debugger; 24 } 25 }, 26 data: function() { 27 28 29 return { 30 displayTime: new Date().getTime(), 31 selectedDate: new Date(2018, 8, 13).getTime(), 32 displayMonthNum: 2 33 } 34 }, 35 // template: `<page-index></page-index>`, 36 template: ` 37 <ui-calendar v-on:dayclick="onDayClick" :selectedDate="selectedDate" :displayMonthNum="displayMonthNum" :displayTime="displayTime" ></ui-calendar> 38 `, 39 el: '#app' 40 }) 41 </script> 42 </body> 43 </html>
1 import data from './ui-calendar-month.js' 2 3 Vue.component(data.name, data.data); 4 5 export default { 6 name: 'ui-calendar', 7 data: { 8 props: { 9 displayMonthNum: { 10 type: Number 11 }, 12 displayTime: { 13 type: Number 14 }, 15 selectedDate: { 16 type: Number 17 } 18 }, 19 methods: { 20 }, 21 data: function() { 22 //要求傳入的當前顯示時間必須是時間戳 23 let date = new Date(this.displayTime); 24 25 return { 26 year: date.getFullYear(), 27 month: date.getMonth(), 28 weekDayArr: ['日', '一', '二', '三', '四', '五', '六'] 29 } 30 }, 31 template: 32 ` 33 <ul class="cm-calendar "> 34 <ul class="cm-calendar-hd"> 35 <template v-for="i in 7" > 36 <li class="cm-item--disabled">{{weekDayArr[i-1]}}</li> 37 </template> 38 </ul> 39 <template v-for="m in displayMonthNum" > 40 <ui-calendar-month :selectedDate="selectedDate" :year="year" :month="month+m-1" ></ui-calendar-month> 41 </template> 42 </ul> 43 ` 44 } 45 }
1 import data from './ui-calendar-day.js' 2 3 Vue.component(data.name, data.data); 4 5 //公共方法抽離,輸入年月日判斷在今天前仍是今天后 6 function isLeapYear(year) { 7 if ((typeof year == 'object') && (year instanceof Date)) year = year.getFullYear() 8 if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) return true; 9 return false; 10 } 11 12 // @description 獲取一個月份1號是星期幾,注意此時保留開發習慣,月份傳入時須要自主減一 13 // @param year {num} 多是年份或者爲一個date時間 14 // @param year {num} 月份 15 // @return {num} 當月一號爲星期幾0-6 16 function getBeginDayOfMouth(year, month) { 17 //自動減一以便操做 18 month--; 19 if ((typeof year == 'object') && (year instanceof Date)) { 20 month = year.getMonth(); 21 year = year.getFullYear(); 22 } 23 var d = new Date(year, month, 1); 24 return d.getDay(); 25 } 26 27 export default { 28 name: 'ui-calendar-month', 29 data: { 30 props: { 31 year: { 32 type: Number 33 }, 34 month: { 35 type: Number 36 }, 37 selectedDate: { 38 type: Number 39 } 40 }, 41 methods: { 42 43 }, 44 data: function() { 45 let days = [31, isLeapYear(this.year) ? 29 : 28, 46 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; 47 //本月從哪天開始 48 let beforeDays = getBeginDayOfMouth(this.year, parseInt(this.month) + 1); 49 50 return { 51 days: days[this.month], 52 beforeDays: beforeDays 53 } 54 }, 55 template: 56 ` 57 <ul class="cm-calendar-bd "> 58 <h3 class="cm-month js_month">{{year + '-' + month}}</h3> 59 <ul class="cm-day-list"> 60 <template v-for="n in beforeDays" > 61 <li class="cm-item--disabled"></li> 62 </template> 63 <template v-for="num in days" > 64 <ui-calendar-day :selectedDate="selectedDate" :year="year" :month="month" v-bind:day="num" ></ui-calendar-day> 65 </template> 66 </ul> 67 </ul> 68 ` 69 } 70 }
1 //公共方法抽離,輸入年月日判斷在今天前仍是今天后 2 function isOverdue(year, month, day) { 3 let date = new Date(year, month, day); 4 let now = new Date(); 5 now = new Date(now.getFullYear(), now.getMonth(), now.getDate()); 6 return date.getTime() < now.getTime(); 7 } 8 9 function isToday(year, month, day, selectedDate) { 10 let date = new Date(year, month, day); 11 return date.getTime() == selectedDate; 12 } 13 14 export default { 15 name: 'ui-calendar-day', 16 data: { 17 props: { 18 year: { 19 type: Number 20 }, 21 month: { 22 type: Number 23 }, 24 day: { 25 type: Number 26 }, 27 selectedDate: { 28 type: Number 29 } 30 }, 31 methods: { 32 onDayClick: function (e) { 33 let data = e.currentTarget.dataset; 34 this.$parent.$parent.$emit('dayclick', data); 35 } 36 }, 37 //引入計算屬性概念 38 computed: { 39 // 計算屬性的 getter 40 klass: function () { 41 //是否過時了 42 let klass = isOverdue(this.year, this.month, this.day) ? 'cm-item--disabled' : ''; 43 44 if(isToday(this.year, this.month, this.day, this.selectedDate)) klass += 'active' 45 return klass; 46 } 47 }, 48 data: function() { 49 return {} 50 }, 51 template: 52 ` 53 <li :selectedDate="selectedDate" @click="onDayClick" :class="klass" v-bind:data-year="year" v-bind:data-month="month" v-bind:data-day="day"> 54 <div class="cm-field-wrapper "><div class="cm-field-title">{{day}}</div></div> 55 </li> 56 ` 57 } 58 }
至此雖然,咱們這塊代碼簡陋,卻完成了一個簡單日曆組件,至此咱們第一階段的探索結束,明天繼續研究