JS实现原生组件开发

234 阅读1分钟

实现一个类继承HTMLElement

class MRadio extends HTMLElement {
    // 如果子类重写了constructor,则必须调用父类的构造函数 spuer()
	constructor () {
	    super()
	}
}
// 注册自定义组件
customElements.define('mt-radio',MRadio)

创建影子DOM

init () {
    let shadow = this.attachShadow({
        mode: 'open' // 指定为开放的封装模式
    })
}

组件 - 数据部分

this.state = {}
try {
    this.state.data = JSON.parse(this.getAttribute('data'))
    this.state.index = this.getAttribute('index') || 0
} catch (error) {
    this.state.data = []
    this.state.index = 0
}

组件 - 事件部分

// 因为DOM元素设置成开放模式所以获取dom的根结点需要用this.shadowRoot
event () {
    this.shadowRoot.addEventListener('click', e  => {
	if(e.target.tagName.toUpperCase() == 'LI'){
            this.state.index = e.target.dataset.n
            this._render()
	} 
	})
}

组件 - 样式部分

style () {
	let style = document.createElement('style')
	style.textContent = `
    .box{
        margin:100px auto 0;
        width: 80%;
    }
    .clear:after{
    	content: '';
    	display: block;
    	clear: both;
    }
    .radio{
    	list-style: none;
    	display: inline-block;
    	margin: 0;
    	padding: 0;
    }
    .radio .item{
    	float: left;
    	padding: 12px 25px;
    	margin-left: 1px;
    	height: 30px;
    	border: 1px solid #ccc;
    	background: #fff;
    	text-align: center;
    	font-size: 16px;
    	line-height: 30px;
    	transition: .2s all;
    	cursor: pointer;
    }
    .radio .item:hover{
    	box-shadow: inset 0 0 130px #409eff;
    	color: #fff;
    }
    .radio .item:first-child{
    	border-radius: 5px 0 0 5px;
    }
    .radio .item:last-child{
    	border-radius: 0 5px 5px 0;
    }
    .radio .item.checked{
    	box-shadow: inset 0 0 130px #409eff;
    	color: #fff;
    }
	`
    this.shadowRoot.appendChild(style)
}

组件 - 结构部分

html () {
    let container = document.createElement('div')
    container.classList.add('box')
    this.shadowRoot.appendChild(container)
    this._render()
} 
_render () {
    let container = this.shadowRoot.querySelector('div.box')
    container.innerHTML = this.render()
}
render () {
    let str = this.state.data.map((item,n) => {
    	if (n == this.state.index) {
    	    return `<li class="item checked" data-n=${n}>${item}</li>`
    	} else {
    	    return `<li class="item" data-n=${n}>${item}</li>`
    	}
    }).join('')
    return `<ul class="radio clear">${str}</ul>`
}

完整的代码

<body>
    <mt-radio data='["北京","上海","广州","深圳"]' index=0></mt-radio>
    <mt-radio data='["前端","后端"]' index=1></mt-radio>
</body>
<script>
class MRadio extends HTMLElement {
    // 如果子类重写了constructor,则必须调用父类的构造函数 spuer()
    constructor () {
    	super()
    	this.init()
    }
    
    // 创建影子DOM
    init () {
    	let shadow = this.attachShadow({
    		mode: 'open'
    	})
        // 获取数据
    	this.data()
        // 注册事件
    	this.event()
        // 设置样式
    	this.style()
        // 生成DOM元素
    	this.html()
    }
    		
    data () {
        this.state = {}
        try {
            this.state.data = JSON.parse(this.getAttribute('data'))
            this.state.index = this.getAttribute('index') || 0
        } catch (error) {
            this.state.data = []
            this.state.index = 0
        }
    }
    		
    event () {
        this.shadowRoot.addEventListener('click', e  => {
    	    if(e.target.tagName.toUpperCase() == 'LI'){
                this.state.index = e.target.dataset.n
                this._render()
            } 
    	})
    }
    		
    style () {
    	let style = document.createElement('style')
    	style.textContent = `
    	.box{
    		margin:100px auto 0;
    		width: 80%;
    	}
    	.clear:after{
    		content: '';
    		display: block;
    		clear: both;
    	}
    	.radio{
    		list-style: none;
    		display: inline-block;
    		margin: 0;
    		padding: 0;
    	}
    	.radio .item{
    		float: left;
    		padding: 12px 25px;
    		margin-left: 1px;
    		height: 30px;
    		border: 1px solid #ccc;
    		background: #fff;
    		text-align: center;
    		font-size: 16px;
    		line-height: 30px;
    		transition: .2s all;
    		cursor: pointer;
    	}
    	.radio .item:hover{
    		box-shadow: inset 0 0 130px #409eff;
    		color: #fff;
    	}
    	.radio .item:first-child{
    		border-radius: 5px 0 0 5px;
    	}
    	.radio .item:last-child{
    		border-radius: 0 5px 5px 0;
    	}
    	.radio .item.checked{
    		box-shadow: inset 0 0 130px #409eff;
    		color: #fff;
    	}
    	`
    	this.shadowRoot.appendChild(style)
    }
    		
    html () {
    	let container = document.createElement('div')
    	container.classList.add('box')
    	this.shadowRoot.appendChild(container)
    	this._render()
    } 
            	        
    _render () {
    	let container = this.shadowRoot.querySelector('div.box')
    	container.innerHTML = this.render()
    }
            
    render () {
    	let str = this.state.data.map((item,n) => {
    		if (n == this.state.index) {
    			return `<li class="item checked" data-n=${n}>${item}</li>`
    		} else {
    			return `<li class="item" data-n=${n}>${item}</li>`
    		}
    	}).join('')
    	return `<ul class="radio clear">${str}</ul>`
    }
    }
    	customElements.define('mt-radio',MRadio)
</script>