組件化實(shí)戰(zhàn)——組件知識(shí)和基礎(chǔ)輪播組件
點(diǎn)擊上方 前端Q,關(guān)注公眾號(hào)
回復(fù)加群,加入前端Q技術(shù)交流群
作者:blateyang
原文鏈接:https://juejin.cn/post/6986304993171079176/
1.1. 組件的基本知識(shí)
1.1.1 前端兩大重點(diǎn)內(nèi)容
組件化:解決復(fù)用問(wèn)題
架構(gòu)模式:如MVC、MVVM等,解決前端和數(shù)據(jù)邏輯層的交互問(wèn)題
1.1.1.2 組件的理解
組件可以看作特殊的對(duì)象和模塊,它和UI是強(qiáng)相關(guān)的,是可復(fù)用的界面功能模塊。它除了具有對(duì)象的property,method,inherit之外,還有attribute,state,children,event等,下圖描述了組件組成部分間的關(guān)系
1.1.1.3 Attribute vs Property
Attribute強(qiáng)調(diào)描述性
Property強(qiáng)調(diào)從屬關(guān)系
在html中,二者含義是不同的
<input value="cute">
<script>
var input = document.getElementByTagName("input"); // 若property沒(méi)有設(shè)置,則結(jié)果是attribute
input.value //cute
input.getAttribute("value"); //cute
input.value = "hello"; //若value屬性已設(shè)置,則attribute不變,property變化,元素上實(shí)際的效果是property優(yōu)先
input.value // hello
input.getAttribute("value"); //cute
</script>
1.1.1.4 如何設(shè)計(jì)組件狀態(tài)(?表示待定)
| Markup set | JS set | JS Change | User Input Change | |
|---|---|---|---|---|
| property | x | √ | √ | ? |
| attribute | √ | √ | √ | ? |
| state | x | x | x | √ |
| config | x | √ | x | x |
1.1.1.5 生命周期Lifecycle
1.2 為組件添加jsx語(yǔ)法
1.2.1 搭建支持jsx語(yǔ)法的環(huán)境
jsx是babel的插件,因此要依次安裝webpack,babel-loader, babel和babel-plugin
安裝webpack,用于靜態(tài)模塊打包
npm install -g webpack webpack-cli
安裝babel-loader,用于將其他語(yǔ)言的代碼轉(zhuǎn)譯成webpack能夠識(shí)別的語(yǔ)言(js或json)
npm install --save-dev webpack babel-loader
安裝babel用于將新版本的js編譯成舊版本的js以便能跑在舊版本的瀏覽器中
npm install --save-dev @babel/core @babel/preset-env
安裝react-jsx插件用于在js中能夠使用jsx
npm install --save-dev @babel/plugin-transform-react-jsx
安裝完后還要在webpack.config.js中將安裝的各種庫(kù)填寫(xiě)進(jìn)配置文件中,如下
module.exports = {
entry: "./main.js",
module: {
rules: [
{
test: /\.js$/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
plugins: ["@babel/plugin-transform-react-jsx"]
}
}
}
]
},
mode: "development"
}
1.3 JSX的基本使用方法
1.3.1 JSX的原理
利用babel/plugin-transform-react-jsx插件將html標(biāo)簽寫(xiě)法轉(zhuǎn)換成創(chuàng)建dom樹(shù)的函數(shù)調(diào)用createElement(type, attributes, ...children)
1.3.2 createElement函數(shù)的基本實(shí)現(xiàn)
function createElement(type, attributes, ...children) {
let element = document.createElement(type)
for(let attr in attributes) {
element.setAttribute(attr, attributes[attr])
}
for(let child of children) {
if(typeof child === "string") {
child = document.createTextNode(child)
}
element.appendChild(child)
}
return element
}
1.3.3 增加對(duì)自定義標(biāo)簽的支持
function createElement(type, attributes, ...children) {
let element
if(typeof type === "string")
element = document.createElement(type)
else
element = new type()
for(let attr in attributes) {
element.setAttribute(attr, attributes[attr])
}
for(let child of children) {
if(typeof child === "string") {
child = document.createTextNode(child)
}
element.appendChild(child)
}
return element
}
class Div{
constructor() {
this.root = document.createElement("section")
}
setAttribute(name, value) {
this.root.setAttribute(name, value)
}
appendChild(child) {
this.root.appendChild(child)
}
mountTo(parent) {
parent.appendChild(this.root)
}
}
let a = <Div id="a">
<span>a</span>
<span>b</span>
<span>c</span>
</Div>
// document.body.appendChild(a)
a.mountTo(document.body)
1.3.4 給原生html標(biāo)簽添加包裝類(lèi)使其支持mountTo方法
function createElement(type, attributes, ...children) {
let element
if(typeof type === "string")
element = new ElementWrapper(type)
else
element = new type()
for(let attr in attributes) {
element.setAttribute(attr, attributes[attr])
}
for(let child of children) {
if(typeof child === "string") {
child = new TextWrapper(child)
}
child.mountTo(element)
}
return element
}
class ElementWrapper{
constructor(type) {
this.root = document.createElement(type)
}
setAttribute(name, value) {
this.root.setAttribute(name, value)
}
appendChild(child) {
child.mountTo(this.root);
}
mountTo(parent) {
parent.appendChild(this.root)
}
}
class TextWrapper{
constructor(content) {
this.root = document.createTextNode(content)
}
setAttribute(name, value) {
this.root.setAttribute(name, value)
}
appendChild(child) {
child.mountTo(this.root);
}
mountTo(parent) {
parent.appendChild(this.root)
}
}
class Div{
constructor() {
this.root = document.createElement("section")
}
setAttribute(name, value) {
this.root.setAttribute(name, value)
}
appendChild(child) {
child.mountTo(this.root);
}
mountTo(parent) {
parent.appendChild(this.root)
}
}
let a = <Div id="a">
<span>a</span>
<span>b</span>
<span>c</span>
</Div>
// document.body.appendChild(a)
a.mountTo(document.body)
2 動(dòng)手實(shí)現(xiàn)一個(gè)輪播組件
class Component {
constructor(type) {
// this.root = this.render()
}
setAttribute(name, value) {
this.root.setAttribute(name, value)
}
appendChild(child) {
this.root.appendChild(child)
}
mountTo(parent) {
parent.appendChild(this.root)
}
}
class Carousel extends Component{
constructor() {
super()
this.attributes = Object.create(null) // 創(chuàng)建空對(duì)象接收src屬性
}
setAttribute(name, value) { // 重寫(xiě)基類(lèi)的方法,以便將src設(shè)置到組件中
this.attributes[name] = value
}
render() {
// console.log(this.attributes.src)
this.root = document.createElement("section")
this.root.classList.add("carousel")
for(let item of this.attributes.src) {
let img = document.createElement("section")
img.style.backgroundImage = `url(${item})`
this.root.appendChild(img)
}
let currentIdx = 0
let time = 3000
let children = this.root.children
// 自動(dòng)輪播, 每次只需移動(dòng)viewport中的兩張相鄰圖片
let timer = setInterval(()=> {
let nextIdx = (currentIdx + 1) % children.length
let current = children[currentIdx]
let next = children[nextIdx]
// next快速移入viewport的后一個(gè)位置
next.style.transition = "none"
next.style.transform = `translateX(${100 - nextIdx*100}%)`
setTimeout(()=>{// 實(shí)現(xiàn)current移出viewport,next移入viewport且next快速切換到current
next.style.transition = "" // 恢復(fù)樣式表中的transition設(shè)置
current.style.transform = `translateX(${-100 - currentIdx*100}%)`
next.style.transform = `translateX(${-nextIdx*100}%)`
currentIdx = nextIdx
}, 16) // 此處設(shè)置延時(shí)是為了避免立即覆蓋前面對(duì)next的設(shè)置
}, time)
// 手動(dòng)輪播
let position = 0
this.root.addEventListener("mousedown", (event) => {
let startX = event.clientX
console.log("mousedown")
let move = (event)=>{
console.log("mousemove")
let x = event.clientX - startX
let current = position
for(let offset of [-1, 0, 1]) {
let pos = current + offset
pos = (pos + children.length) % children.length // 將索引-1變?yōu)?
children[pos].style.transition = "none" // 拖動(dòng)時(shí)關(guān)閉過(guò)渡效果
children[pos].style.transform = `translateX(${-pos*500 + offset*500 + x%500}px)`
}
}
let up = (event) => {
let x = event.clientX - startX
let current = position - Math.round(x/500) // 獲取松手時(shí)就近的幀索引
for(let offset of [0, Math.sign(Math.abs(x)>250 ? x:-x)]) {
// 拖動(dòng)距離大于視口的一半,當(dāng)前圖片和下一張圖片跟著移動(dòng),否則當(dāng)前圖片和上一張圖片跟著移動(dòng)
let pos = current + offset
pos = (pos + children.length) % children.length // 將索引-1變?yōu)?
children[pos].style.transition = "" // 恢復(fù)過(guò)渡效果
children[pos].style.transform = `translateX(${-pos*500 + offset*500}px)`
}
console.log("mouseup")
document.removeEventListener("mousemove", move)
document.removeEventListener("mouseup", up)
}
// 在document上監(jiān)聽(tīng)可以防止移出圖片區(qū)域無(wú)法響應(yīng)監(jiān)聽(tīng)事件
document.addEventListener("mousemove", move)
document.addEventListener("mouseup", up)
})
return this.root
}
mountTo(parent) {
parent.appendChild(this.render())
}
}
let d = ['https://res.devcloud.huaweicloud.com/obsdevui/diploma/8.1.17.002/banner-8.d14241daf518717981c6.jpg',
'https://res.devcloud.huaweicloud.com/obsdevui/diploma/8.1.17.002/banner-1.fdbf82d4c2ca7fad3225.jpg',
'https://res.devcloud.huaweicloud.com/obsdevui/diploma/8.1.17.002/banner-2.64b1407e7a8db89d6cf2.jpg',
'https://res.devcloud.huaweicloud.com/obsdevui/diploma/8.1.17.002/banner-3.ce76c93c7a8a149ce2a2.jpg']
let a = <Carousel src=go7utgvlrp/>
a.mountTo(document.body)
注:自動(dòng)輪播中下面兩行代碼目的是在每一次輪播時(shí)提前將下一張輪播圖移到viewport右側(cè),以便在視覺(jué)上能夠形成輪播的效果,如下圖所示,a,b,c,d是四張圖,每一行代表transform后的一次狀態(tài),虛線(xiàn)箭頭表示transition為none時(shí)的移動(dòng)過(guò)程
next.style.transition = "none"
next.style.transform = `translateX(${100 - nextIdx*100}%)`
ps:如果覺(jué)得此文對(duì)你有幫助或啟發(fā),請(qǐng)順手點(diǎn)贊和分享,這是對(duì)我的最大鼓勵(lì),如有疑問(wèn),請(qǐng)留言或私信交流,看到會(huì)及時(shí)回復(fù)
內(nèi)推社群
我組建了一個(gè)氛圍特別好的騰訊內(nèi)推社群,如果你對(duì)加入騰訊感興趣的話(huà)(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行面試相關(guān)的答疑、聊聊面試的故事、并且在你準(zhǔn)備好的時(shí)候隨時(shí)幫你內(nèi)推。下方加 winty 好友回復(fù)「面試」即可。
