1. 簡介
Material 元件 (MDC) 可協助開發人員實作 Material Design。MDC 是由 Google 的工程師和使用者體驗設計師團隊所開發,提供數十種美觀實用的 UI 元件,適用於 Android、iOS、網頁和 Flutter。material.io/develop |
MDC Web 的設計目的是整合至任何前端架構,同時維持 Material Design 原則。以下程式碼研究室會引導您建構 React 元件,該元件會使用 MDC Web 做為基礎。本程式碼研究室中學到的原則,可套用至任何 JavaScript 架構。
MDC Web 的建構方式
MDC Web 的 JavaScript 層的每個元件由三個類別組成:元件、基礎和轉接器。這個模式可讓 MDC Web 彈性地整合前端架構。
Foundation 包含實作 Material Design 的商業邏輯。基礎架構不會參照任何 HTML 元素。這可讓我們將 HTML 互動邏輯抽象化為Adapter。Foundation 有 Adapter。
Adapter 是介面。Foundation 會參照 Adapter 介面,以便實作 Material Design 業務邏輯。您可以使用 Angular 或 React 等其他架構實作轉接程式。轉接程式的實作會與 DOM 結構互動。
元件具有基礎,其角色是
- 導入轉接程式 (使用非架構 JavaScript),並
- 提供公開方法,以 Proxy 為基礎中的方法。
MDC Web 提供的內容
MDC Web 中的每個套件都會隨附元件、基礎和轉接器。如要將 Component 例項化,您必須將根元素傳遞至 Component 的建構函式方法。元件會實作轉接程式,用於與 DOM 和 HTML 元素互動。接著,Component 會將 Foundation 例項化,並呼叫 Adapter 方法。
如要將 MDC Web 整合至架構,您必須使用該架構的語言/語法建立自己的元件。架構 Component 會實作 MDC Web 的 Adapter,並使用 MDC Web 的 Foundation。
建構項目
本程式碼研究室將示範如何建構自訂的Adapter,以便使用Foundation 邏輯來實現 Material Design React 元件。這門課程涵蓋「整合至架構」中的進階主題。本程式碼研究室使用 React 做為範例架構,但這項做法可套用至任何其他架構。
在本程式碼研究室中,您將建構頂端應用程式列,並重新建立頂端應用程式列示範頁面。示範頁面版面配置已設定完成,因此您可以開始使用頂端應用程式列。頂端應用程式列包括:
- 導覽圖示
- 待辦事項
- 目前提供 4 種變化版本:「短」、「一律收合」、「固定」和「顯眼」的變化版本
事前準備:
針對網站開發的經驗程度,你會給予什麼評價?
2. 設定開發環境
下載程式碼研究室入門應用程式
範例應用程式位於 material-components-web-codelabs-master/mdc-112/starter
目錄中。
...或從 GitHub 複製
如要從 GitHub 複製本程式碼研究室,請執行下列指令:
git clone https://github.com/material-components/material-components-web-codelabs
cd material-components-web-codelabs/mdc-112/starter
安裝專案依附元件
從入門目錄 material-components-web-codelabs/mdc-112/starter
執行:
npm install
您會看到許多活動,最後終端機應會顯示安裝成功:
執行範例應用程式
在同一個目錄中執行:
npm start
webpack-dev-server
就會開始。將瀏覽器指向 http://localhost:8080/ 即可查看該頁面。
大功告成!頂端應用程式列 React 示範頁面的啟動程式碼應會在瀏覽器中執行。您應該會看到一整面 lorem ipsum 文字、一個 控制項方塊 (右下方),以及未完成的頂端應用程式列:
查看程式碼和專案
如果您開啟程式碼編輯器,專案目錄應會如下所示:
開啟 App.js
檔案,然後查看 render
方法,其中包含 <TopAppBar>
元件:
App.js
render() {
const {isFixed, isShort, isRtl, isProminent, isAlwaysCollapsed, shouldReinit} = this.state;
return (
<section
dir={isRtl ? 'rtl' : 'ltr'}
className='mdc-typography'>
{
shouldReinit ? null :
<TopAppBar
navIcon={this.renderNavIcon()}
short={isShort}
prominent={isProminent}
fixed={isFixed}
alwaysCollapsed={isAlwaysCollapsed}
title='Mountain View, CA'
actionItems={this.actionItems}
/>
}
<div className={classnames('mdc-top-app-bar--fixed-adjust', {
'mdc-top-app-bar--short-fixed-adjust': isShort || isAlwaysCollapsed,
'mdc-top-app-bar--prominent-fixed-adjust': isProminent,
})}>
{this.renderDemoParagraphs()}
</div>
{this.renderControls()}
</section>
);
}
這是應用程式中 TopAppBar
的進入點。
開啟檔案 TopAppBar.js
,這是一個含有 render
方法的裸 React Component
類別:
TopAppBar.js
import React from 'react';
export default class TopAppBar extends React.Component {
render() {
return (
<header>
TOP APP BAR
</header>
);
}
}
3. 元件組合
在 React 中,render
方法會輸出元件的 HTML。頂端應用程式列元件會轉譯 <header />
標記,由 2 個主要部分組成:
- 導覽圖示和標題部分
- 動作圖示區段
如果您對頂端應用程式列的組成元素有任何疑問,請參閱 GitHub 的說明文件。
修改 TopAppBar.js
中的 render()
方法,如下所示:
render() {
const {
title,
navIcon,
} = this.props;
return (
<header
className={this.classes}
style={this.getMergedStyles()}
ref={this.topAppBarElement}
>
<div className='mdc-top-app-bar__row'>
<section className='mdc-top-app-bar__section mdc-top-app-bar__section--align-start'>
{navIcon ? navIcon : null}
<span className="mdc-top-app-bar__title">
{title}
</span>
</section>
{this.renderActionItems()}
</div>
</header>
);
}
這個 HTML 中有兩個 section 元素。第一個包含導覽圖示和標題。第二個則包含動作圖示。
接著,新增 renderActionItems
方法:
renderActionItems() {
const {actionItems} = this.props;
if (!actionItems) {
return;
}
return (
<section className='mdc-top-app-bar__section mdc-top-app-bar__section--align-end' role='toolbar'>
{/* need to clone element to set key */}
{actionItems.map((item, key) => React.cloneElement(item, {key}))}
</section>
);
}
開發人員會將 TopAppBar
匯入 React 應用程式,並將動作圖示傳遞至 TopAppBar
元素。您可以在 App.js
中查看初始化 TopAppBar
的程式碼範例。
缺少 getMergedStyles
方法,該方法會在 render
方法中使用。請將下列 JavaScript 方法新增至 TopAppBar
類別:
getMergedStyles = () => {
const {style} = this.props;
const {style: internalStyle} = this.state;
return Object.assign({}, internalStyle, style);
}
render
方法也缺少 this.classes
,但我們會在後續章節中介紹這個方法。除了缺少的 getter 方法 this.classes
以外,您還需要實作 TopAppBar
部分,頂端應用程式列才能正確顯示。
頂端應用程式列中仍缺少的 React 元件部分:
- 已初始化的基礎
- 要傳遞至基礎結構的轉接介面方法
- JSX 標記
- 變化版本管理 (固定、簡短、一律收合、醒目)
方法
- 實作 Adapter 方法。
- 在
componentDidMount
中初始化 Foundation。 - 在
componentWillUnmount
中呼叫 Foundation.destroy 方法。 - 透過結合適當類別名稱的 getter 方法,建立變化版本管理機制。
4. 實作 Adapter 方法
非架構 JS TopAppBar
Component 會實作下列Adapter 方法 (詳情請參閱這裡):
hasClass()
addClass()
removeClass()
registerNavigationIconInteractionHandler()
deregisterNavigationIconInteractionHandler()
notifyNavigationIconClicked()
setStyle()
getTopAppBarHeight()
registerScrollHandler()
deregisterScrollHandler()
registerResizeHandler()
deregisterResizeHandler()
getViewportScrollY()
getTotalActionItems()
由於 React 有合成事件和不同的最佳程式碼編寫做法和模式,因此需要重新實作 Adapter 方法。
轉接器 Getter 方法
在 TopAppBar.js
檔案中,將下列 JavaScript 方法新增至 TopAppBar
:
get adapter() {
const {actionItems} = this.props;
return {
hasClass: (className) => this.classes.split(' ').includes(className),
addClass: (className) => this.setState({classList: this.state.classList.add(className)}),
removeClass: (className) => {
const {classList} = this.state;
classList.delete(className);
this.setState({classList});
},
setStyle: this.setStyle,
getTopAppBarHeight: () => this.topAppBarElement.current.clientHeight,
registerScrollHandler: (handler) => window.addEventListener('scroll', handler),
deregisterScrollHandler: (handler) => window.removeEventListener('scroll', handler),
registerResizeHandler: (handler) => window.addEventListener('resize', handler),
deregisterResizeHandler: (handler) => window.removeEventListener('resize', handler),
getViewportScrollY: () => window.pageYOffset,
getTotalActionItems: () => actionItems && actionItems.length,
};
}
捲動和調整大小事件註冊的轉接器 API 實作方式與非架構 JS 版本相同,因為 React 沒有任何用於捲動或調整大小的合成事件,而是將事件交由原生 DOM 事件系統處理。getViewPortScrollY
也需採用原生 DOM,因為這是 window
物件上的函式,而不在 React 的 API 中。每個架構的轉接程式實作方式都不同。
您可能會發現缺少由 get adapter
方法呼叫的 this.setStyle
。在 TopAppBar.js
檔案中,將缺少的 JavaScript 方法新增至 TopAppBar
類別:
setStyle = (varName, value) => {
const updatedStyle = Object.assign({}, this.state.style);
updatedStyle[varName] = value;
this.setState({style: updatedStyle});
}
您剛剛已實作轉接程式!請注意,目前您的控制台可能會顯示錯誤,因為完整的實作尚未完成。下一節將說明如何新增及移除 CSS 類別。
5. 實作元件方法
管理變化版本和類別
React 沒有可管理類別的 API,如要模擬原生 JavaScript 的 CSS 類別新增/移除方法,請新增 classList
狀態變數。TopAppBar
中有三個與 CSS 類別互動的程式碼:
<TopAppBar />
元件,並透過className
屬性。- 透過
addClass
或removeClass
的 Adapter 方法。 - 在
<TopAppBar />
React 元件內使用硬式編碼。
首先,在 TopAppBar.js
頂端的現有匯入項目下方新增下列匯入項目:
import classnames from 'classnames';
接著在 TopAppBar
元件的類別宣告中加入以下程式碼:
export default class TopAppBar extends React.Component {
constructor(props) {
super(props);
this.topAppBarElement = React.createRef();
}
state = {
classList: new Set(),
style: {},
};
get classes() {
const {classList} = this.state;
const {
alwaysCollapsed,
className,
short,
fixed,
prominent,
} = this.props;
return classnames('mdc-top-app-bar', Array.from(classList), className, {
'mdc-top-app-bar--fixed': fixed,
'mdc-top-app-bar--short': short,
'mdc-top-app-bar--short-collapsed': alwaysCollapsed,
'mdc-top-app-bar--prominent': prominent,
});
}
...
}
如果您前往 http://localhost:8080,「Control」核取方塊現在應切換為開啟/關閉 DOM 的類別名稱。
這段程式碼可讓許多開發人員使用 TopAppBar
。開發人員可以與 TopAppBar
API 互動,不必擔心 CSS 類別的實作細節。
您已成功實作轉接程式。下一節將引導您建構 Foundation 的例項。
掛載及卸載元件
Foundation 會在 componentDidMount
方法中進行例項化。
首先,請在 TopAppBar.js
現有的匯入項目後方新增下列匯入項目,匯入 MDC Top App Bar 基礎:
import {MDCTopAppBarFoundation, MDCFixedTopAppBarFoundation, MDCShortTopAppBarFoundation} from '@material/top-app-bar';
接下來,請將下列 JavaScript 程式碼加入 TopAppBar
類別:
export default class TopAppBar extends React.Component {
...
foundation_ = null;
componentDidMount() {
this.initializeFoundation();
}
componentWillUnmount() {
this.foundation_.destroy();
}
initializeFoundation = () => {
if (this.props.short) {
this.foundation_ = new MDCShortTopAppBarFoundation(this.adapter);
} else if (this.props.fixed) {
this.foundation_ = new MDCFixedTopAppBarFoundation(this.adapter);
} else {
this.foundation_ = new MDCTopAppBarFoundation(this.adapter);
}
this.foundation_.init();
}
...
}
定義 propTypes 和 defaultProps 是良好的 React 程式設計做法。在 TopAppBar.js 中現有的匯入項目後方,新增下列匯入項目:
import PropTypes from 'prop-types';
接著,在 TopAppBar.js
底部 (元件類別之後) 新增下列程式碼:
import PropTypes from 'prop-types';
TopAppBar.propTypes = {
alwaysCollapsed: PropTypes.bool,
short: PropTypes.bool,
fixed: PropTypes.bool,
prominent: PropTypes.bool,
title: PropTypes.string,
actionItems: PropTypes.arrayOf(PropTypes.element),
navIcon: PropTypes.element,
};
TopAppBar.defaultProps = {
alwaysCollapsed: false,
short: false,
fixed: false,
prominent: false,
title: '',
actionItems: null,
navIcon: null,
};
您現在已成功實作頂端應用程式列 React 元件。前往 http://localhost:8080 即可操作示範頁面。這個示範頁面的運作方式與 MDC Web 的示範頁面相同。示範頁面應如下所示:
6. 總結
在本教學課程中,我們將說明如何包裝 MDC Web 的 Foundation,以便在 React 應用程式中使用。GitHub 和 npm 中的幾個程式庫會包裝 MDC Web 元件,如整合至架構中所述。建議您使用這裡的清單。除了 React 之外,這份清單也包含其他架構,例如 Angular 和 Vue。
本教學課程重點說明我們決定將 MDC Web 程式碼分成 3 個部分,分別是「基礎」、「轉接器」和「元件」。此架構可讓元件在與所有架構搭配運作時,共用通用程式碼。感謝您試用 Material Components React,歡迎查看我們的新程式庫 MDC React。希望您喜歡這個程式碼研究室!