[專案實戰,原始碼完整]手把手教你怎么封裝組件,React 重寫學成在線 III
- 前情回顧
- 進行業務分析
- 內容實作
- subHeader 的實作
- 實作基礎結構
- 什么是 props
- 嘗試渲染
- 設計資料型別
- 修改實作方法,重新渲染 sub header
- 使用 useState 去實作選中高亮
- 精品推薦和課程推薦 的實作
- 封裝課程組件
- 課程主體
- 完整實作課程
- 添加課程串列的資料
- 原始碼
- 總結
看完這篇教程,你能學到以下的知識點:
-
根據業務需求拆分組件
-
對復用組件有所了解
-
子組件如何接受父組件傳來的資料——上一期學的是怎么從父組件傳遞資料給子組件
-
能使用 useState hook 進行狀態管理
-
使用 JavaScript 中的類
-
使用 useHistory 鉤子函式
-
使用 history.push() 完成頁面的重定向
注: useState 對應 class based component 中的狀態,是一個效果非常強大的鉤子函式
前情回顧
系列文的第一篇 [萬字長文]使用 React 重寫學成在線前端專案 I 代碼完整可運行,步驟有詳解 中,主要完成了關于 Header 和 Footer 的封裝:
在上一篇 還在煩惱沒有專案?手把手帶你從 0 開始用 React 重寫學成在線 II 中,實作了完成了 banner 的動態實作 和 下面的精品推薦 部分:
在插播的內容 寫好了功能/專案不知道怎么展示?手把手帶你白嫖 Git Pages 部署自己的專案去驚艷面試官 中講了如何利用 GitHub 去部署自己的專案,這個專案的在線瀏覽地址在這里:https://goldenaarcher.com/xczx-react/#/,
到這里,整個頁面已經可以說完成了 40%了,只要再模塊化三四個組件,首頁剩下的內容幾乎都能夠動態生成,
進行業務分析
在開始具體的實作之前,我們還是應該對業務進行分析,看看怎么實作才能夠達到最大效果的代碼復用,
首先,看看剩下還沒有實作的幾個模塊:
-
一個精品推薦
-
一個領域推薦
-
一個領域推薦
-
一個課程推薦
-
一個課程推薦
-
一個導師推薦
可以看出,整體的結構其實很相似,都是一個 header + 內容 的表現方式,而且所有的 header 的樣式基本是一樣的,只是包含的內容多少而已,
拆分一下,header 的格式就是這樣的:
最左邊的 title + 中間的熱門點擊 + 最右邊的查看更多
所以第一步就先將這個 header 封裝出來,
內容區其實也可以被合理的分成 3 個大模塊:
-
精品推薦和課程推薦
這里其實能看到,精品推薦和課程推薦中的內容區從結構上來說都是一樣的——都是一排排的課程
也因此可以將這兩個內容合并成同一個模塊,讓它們渲染給定的課程即可
對比:
精品推薦 課程推薦 

-
領域推薦
兩個領域的結構也是一樣的,分為:
- 左邊的 banner
- 上面的 banner
- 一系列的課程推薦
鑒于這一塊的布局會和 精品/課程 推薦不一樣,所以再單獨拆分一個模塊
-
導師推薦
這個是和其他的模塊差異最大的,自然而然需要被分出來
這一期內容的目的就是:
- 完成 sub header 的封裝
- 完成 精品推薦和課程推薦 的基礎實作
內容實作
這次就只完成一個模塊:
當完成了之后你就會發現,剩下的內容渲染起來是真的很快,
subHeader 的實作
我把出現的 3 個 subHeader 全都放在了一起,這么一對比,應該就能看出結構是多么的相似,并且是可以實作復用的:
畢竟,subHeader 分割下來其實有三個部分:最左邊的標題,中間的選項,以及右邊的查看更多,
實作基礎結構
首先,新建一個組件,這個組件需要明確接收 3 個值:
-
最左的標題,必須
這里命名為 subHeaderName
-
中間的選取,可選
這里命名為 midConent
-
右側的查看更多,可選
這里命名為 checkMore,指查看更多
代碼實作:
import React from 'react';
const SubHeader = (props) => {
const { subHeaderName, midConent, checkMore } = props;
return (
<div className="flex space-between">
<div>{subHeaderName}</div>
<div>{midConent.map((val) => val)}</div>
<div>{checkMore}</div>
</div>
);
};
export default SubHeader;
什么是 props
這里的 props 是用來接收父組件傳來的資料的,在上一期輪播圖中講了父組件如何向子組件傳遞資料,這里講的就是子組件如何從父組件中接收資料,
父組件向子組件傳遞資料的方法:
const settings = {
data: 'test',
}
<Child {...settings} />
父組件傳到子組件的這些屬性最侄訓被 React 壓縮到一個物件中,約定俗成的這個物件叫做 props,這也是上文使用的,
從 props 中獲取資料的方法也很簡單,就像從一般的物件中獲取屬性一樣,直接使用 props.data,或者像上文一樣解構資料都可以獲得:
// using props directly
alert(props.data);
// using object destruct
const { data } = props;
alert(data);
嘗試渲染
加上一點點 CSS 后,做一個測驗資料來看看渲染效果吧,
先從父組件中傳遞一些假資料進去:
const Home = () => {
return (
// 省略其他
<div className="homepage-main">
<SubHeader
subHeaderName="編程入門"
midConent={[123]}
checkMore={'true'}
/>
</div>
);
};
看起來效果還不錯,現在需要設計的就是傳進去的資料型別了,
設計資料型別
已知 sub header 會接受 3 個資料型別:subHeaderName, midConent, 和 checkMore,
-
subHeaderName是一個純字串,這個也是最簡單的, -
midConent最終的決定是由一個由 物件 組成的陣列:[ { title: '熱門', url: '/#' }, { title: '初級', url: '/#' }, { title: '中級', url: '/#' }, { title: '高級', url: '/#' }, ]; -
checkMore則是單獨的一個物件:{ title: '查看全部', url: '/#', };
其實為了省事兒單獨的寫 <a href='/#> 一些標題 </a> 也不是不可以,嫌麻煩的也可以直接這么寫,
這種就屬于每個人都會不太一樣的編碼風格和習慣問題,對于我來說將這些常量提取出去,一旦要修改或是清理資料都會方便很多,畢竟真實的專案中,如果后臺資料沒有準備好的話,大多數情況下是要自己準備 偽資料,而不是等后臺開發完畢之后再使用后臺傳來的資料進行開發,
?? 所以,一定要和你的后臺提前溝通好傳來的資料結構是什么樣子的,有檔案最好,沒有檔案 一定 要留下郵件或是微信聊天記錄作為證據,以防 被甩鍋,
修改實作方法,重新渲染 sub header
中間的內容區依舊會使用 ul > li > a 的經典結構,并且使用 arr.map() 去進行遍歷,
import React from 'react';
const SubHeader = (props) => {
const { subHeaderName, midConent, checkMore } = props;
const getMidContent = () => {
return (
<ul className="flex">
{midConent.map((val) => (
<li key={val.title}>
<a href={val.url}>{val.title}</a>
</li>
))}
</ul>
);
};
return (
<div className="sub-header flex space-between flex-center">
<h3>{subHeaderName}</h3>
{getMidContent()}
<div>
<a href={checkMore.url}>{checkMore.title}</a>
</div>
</div>
);
};
到這一步,距離 sub header 實作完畢還差最后一步——選中高亮的實作,
以下為靜態的實作效果:
使用 useState 去實作選中高亮
選中高亮的邏輯實作起來其實并不復雜,具體步驟如下:
- 需要一個變數保存當前選中的選項,默認為
熱門 - 在點擊事件發生時,需要替換被選中的選項
這個部分的實作就需要借助 React 自帶的一個鉤子函式:setState,setState 的存在是為了替換 類組件(class-based component) 中的 狀態(state) 屬性,
語法為:
建議使用 ES6 中的 const
const [property, propertyHandler] = useState(defaultValue);
其中
-
property為該屬性的變數名 -
propertyHandler為事件發生時用來更新狀態的函式這個函式只是一個識別符號,它不負責處理其他的邏輯,只負責更新傳進去的值
-
useState中傳進去的為默認值 -
在需要更新狀態的事件函式中呼叫
propertyHandler即可,
以本業務場景為例,初始值的代碼如下;
const [activeTitle, setTitle] = useState(midConent[0].title);
// 點擊事件
function clickHandler(e, val) {
// 防止新頁面打開 and/or 重定向的發生
e.preventDefault();
// 更新狀態
setTitle(val);
}
-
activeTitle定義的是當前被選中,需要被設為高亮的選項 -
setTitle為 點擊標簽時設定當前高亮選項 的函式它會接受一個值,并且直接替換
activeTitle -
useState設定的是默認值,在這個情況下也就是熱門——第一個選項的標題
點擊事件 clickHandler 會還給每一個 a 標簽,在 a 標簽被滑鼠點擊后就會觸發這個狀態,更新當前顯示高亮的標簽,
實作后的動態效果:
到這一步,標題的實作就完成了,
完整 JSX 代碼如下:
-
SubHeader
import React, { useState } from 'react'; import PropTypes from 'prop-types'; const SubHeader = (props) => { // =================新增部分=================== const [activeTitle, setTitle] = useState(midConent[0].title); function clickHandler(e, val) { e.preventDefault(); setTitle(val); } // =================新增部分=================== const getMidContent = () => { return ( <ul className="flex"> {midConent.map((val) => { const { title } = val; return ( <li key={title}> <a href={val.url} onClick={(e) => clickHandler(e, title)} className={activeTitle === title ? 'selected' : null} > {title} </a> </li> ); })} </ul> ); }; }; export default SubHeader; -
Home
const Home = () => { return ( <div className="homepage relative"> <HomeBanner /> <div className="container"> <FieldSuggestion /> <div className="homepage-main"> <SubHeader subHeaderName="編程入門" midConent={subHeaderOl} checkMore={checkMore} /> </div> </div> </div> ); };
精品推薦和課程推薦 的實作
標題已經實作了,剩下的內容就是一排排的課程:
很明顯,每一個課程的結構都是一樣的:
- 課程圖片
- 課程標題
- 課程描述
- 新款課程(右上角的小標題)
- 爆款課程(右上角的小標題)
那么,第一步可以先將單獨的 課程 封裝起來,隨后再回圈遍歷所有的課程串列,就能夠獲得一排排的課表了,
封裝課程組件
這就是之前在 common 里就設計好的 CourseItem 組件,
根據上面列出的課程結構,所以課程組件會需要獲取以下這些屬性:
- id,上文沒顯示的內容,這里是作為一個識別符號而存在的
- title, 標題,也就是課程名稱
- img, 課程圖片
- peopleStudying,多少人在學習,屬于課程描述的內容
- courseLevel,初級中級高級,也屬于課程描述的內容
- isNew 或 isHot,用或是因為 PSD 的設計中只顯示了一個,不過具體的檢查我偷懶了,也沒做
課程主體
暫時不考慮 isNew 或 isHot,先將主體結構實作:
import React from 'react';
const CourseItem = (props) => {
const { peopleStudying, courseLevel, id, title, img, isNew, isHot } = props;
return (
<div className="course-item">
<img src={img} />
<h4>{title}</h4>
<p className="course-info">
<span className="course-level">{courseLevel}</span>· {peopleStudying}
人正在學習
</p>
</div>
);
};
export default CourseItem;
隨后放入假資料:
import php from '../asset/img/courses/php.png';
class Course {
// 偷一下懶,學習人數和等級寫死了
// 也可以傳到建構式中去,但是PSD上都一樣,我就……
peopleStudying = 1125;
courseLevel = '高級';
constructor(id, title, img, isNew = false, isHot = false) {
this.id = id;
this.title = title;
this.img = img;
this.isNew = isNew;
this.isHot = isHot;
}
}
const coursePhp = new Course(1, 'test', php, false, true);
// course list for home page
export const courseSuggestion1 = [coursePhp];
和 CSS:
.course-item {
width: 228px;
height: 270px;
background-color: #fff;
margin: 0 15px 15px 0;
}
.course-img {
width: 100%;
}
.course-item h4 {
margin: 20px;
font-size: 14px;
color: #050505;
font-weight: 400;
}
.course-info {
margin: 0 20px;
font-size: 12px;
color: #999;
}
.course-level {
color: #ff2c2d;
}
這樣一來,效果就已經有了:
完整實作課程
引入之前拉下來的兩個圖片,這里偷懶了沒有檢查排他性——即只能存在 isNew 或 isHot,而不能二者同時存在,
正式開發中如果有需求的話,這個檢查時一定要做的,
然后利用三元表達是去檢查 isNew 和 isHot 是否為 true,如果是的話就渲染對應的組件,如果不是的話就渲染 null,
又因為兩個圖示的效果是完全一致的,我這里繼續抽了一個函式出來去實作 icon:
import React from 'react';
import './courseItem.css';
import hotLabel from '../../asset/img/courses/hot.png';
import newLabel from '../../asset/img/courses/new.png';
const getLabelImg = (img, label) => {
return <img src={img} alt={label} class={label} />;
};
const CourseItem = (props) => {
const { peopleStudying, courseLevel, id, title, img, isNew, isHot } = props;
const isNewCourse = isNew
? getLabelImg(newLabel, 'new-course absolute')
: null;
const isHotCourse = isHot
? getLabelImg(hotLabel, 'hot-course absolute')
: null;
return (
<div className="course-item relative">
<img src={img} alt={title} className="course-img" />
<h4>{title}</h4>
<p className="course-info">
<span className="course-level">{courseLevel}</span>· {peopleStudying}
人正在學習
</p>
{isNewCourse}
{isHotCourse}
</div>
);
};
export default CourseItem;
實作效果:
有點這個意思了,
添加課程串列的資料
一個個手動復制黏貼就是非常浪費時間的事情了,所以下一步就是封裝偽資料,
這里主要就是創建一個課程的類,這樣可以快速的實體化課程,以及創造幾個假資料:
import php from '../asset/img/courses/php.png';
import andriod from '../asset/img/courses/andriod.png';
import angular from '../asset/img/courses/angular.png';
import androidHybrid from '../asset/img/courses/andriod-hybrid.png';
class Course {
// 偷一下懶,學習人數和等級寫死了
// 也可以傳到建構式中去,但是PSD上都一樣,我就……
peopleStudying = 1125;
courseLevel = '高級';
constructor(id, title, img, isNew = false, isHot = false) {
this.id = id;
this.title = title;
this.img = img;
this.isNew = isNew;
this.isHot = isHot;
}
}
const coursePhp = new Course(
1,
'Think PHP 5.0 博客系統實戰專案演練',
php,
false,
true
);
const courseAndriod = new Course(
2,
'Android 網路圖片加載框架詳解',
andriod,
true
);
const courseAngular = new Course(
3,
'Angular 2 最新框架+主流技術+專案實戰',
angular
);
const courseAndroidHybrid = new Course(
4,
'Android Hybrid APP開發實戰 H5+原生!',
androidHybrid
);
const courseAndroidHybrid2 = new Course(
5,
'Android Hybrid APP開發實戰 H5+原生!',
androidHybrid
);
// course list for home page
export const courseSuggestion1 = [
coursePhp,
courseAndriod,
courseAngular,
courseAndroidHybrid,
courseAndroidHybrid2,
];
第一組偽資料就實作好了,接下來在 CourseSuggestion 組建中引入這個串列中,進行回圈遍歷出所有的課程:
import React from 'react';
import SubHeader from '../subHeader';
import { subHeaderOl, checkMore } from '../../../constants/home';
import { courseSuggestion1 } from '../../../constants/courseList';
import CourseItem from '../../../common/courseItem';
// 渲染課程串列,不含其他的內容模塊
const CourseSuggestion = (props) => {
return (
<div>
<SubHeader
subHeaderName="編程入門"
midConent={subHeaderOl}
checkMore={checkMore}
/>
<div className="flex">
{courseSuggestion1.map((course) => (
<CourseItem {...course} key={course.id} />
))}
</div>
</div>
);
};
export default CourseSuggestion;
實作效果如下:
除了差點 CSS 之外,這個味兒對了,
useHistory是react-router-dom提供的一個鉤子函式,可以用來通過它實體化一個history物件,用來實作頁面的重定向,
使用方法如下:
// 參考鉤子函式
import { useHistory } from 'react-router-dom';
// 實體化一個history物件
const history = useHistory();
// 通過將 url 推入 history 完成重定向的操作
history.push(url);
所以,最后在每個課程串列上加入 history.push(url/courseId) 完成重定向,這樣,當用戶點擊單獨的課程串列后,就會被重定向到對應的課程頁面去:
當然,課程頁面還沒有實作,所以看到的就是空白的,
新增的 Course-Item 代碼如下:
// 新增
import { useHistory } from 'react-router-dom';
const CourseItem = (props) => {
// ===========新增===============
const history = useHistory();
const redirectToCourseDetail = () => {
history.push(`${COURSES}/${id}`);
};
// ===========新增===============
return (
// ===============修改================
<div className="course-item relative" onClick={redirectToCourseDetail}>
{/* ===============修改================ */}
<img src={img} alt={title} className="course-img" />
<h4>{title}</h4>
<p className="course-info">
<span className="course-level">{courseLevel}</span> · {peopleStudying}
人正在學習
</p>
{isNewCourse}
{isHotCourse}
</div>
);
};
export default CourseItem;
原始碼
原始碼依舊被打包上傳了:學成在線-react-part3,或者也可以直接到我的 github 上去拉:xczx-react,
總結
這次實作的模塊為以下幾個被紅線畫出來的模塊:
通過這部分的學習,我相信你應該能夠掌握以下的知識點:
-
根據業務需求拆分組件
已經按步拆分啦
-
對復用組件有所了解
回圈遍歷課程是不是很方便?
-
子組件如何接受父組件傳來的資料
建議和上次的父傳子一起復習
-
能使用 useState hook 進行狀態管理
-
使用 JavaScript 中的類
-
使用 useHistory 鉤子函式搭配 history.push() 完成頁面的重定向
接下來是給你的一個挑戰:
封裝都已經實作了,你能不能獨立完成下面的這兩個模塊呢?
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/287245.html
標籤:其他
