介绍
语缺ming_router
ming_router是一个路由库 与 写组件的库,目的是在不使用构建工具的情况下开发单页应用,用ifram是一个很不好的方法,本文介绍两类方案,原生方案,与vue方案。ming_router的组件写法类似react,尽量模拟了react的生命周期函数。
类似的库有 slimjs
安装
项目主页 Gitee
项目地址 Gitee
npm地址 NPM
在线测试
NPM安装
已经说了,不用构建工具,这样安装没意义
npm i ming_router
script引入
<script src="https://unpkg.com/ming_router/src/index.js"></script>
ming_router的对象和方法
| MingRouter.WebComponent | 应用组件需要继承此类 |
mingRouter |
mingRouter.mapping(’/b’,"/b.html") |
MingRouter.Page() |
MingRouter.registWebComponent() |
MingRouter.pageRootPath |
mingRouter.render([path]) |
mingRouter.refresh() |
MingRouter. replaceHash(hash) |
MingRouter.getTemplateByHtmlUrl(htmlUrl) |
mingRouter.renderHtml(html) |
MingRouter.loadHtml(htmlUrl) |
MingRouter.loadCss(cssUrl) |
MingRouter.html(htmlUrl) |
MingRouter.componentMap.组件类名.组件key |
Demo
路由demo
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://unpkg.com/ming_router/src/index.js"></script>
<title>Document</title>
</head>
<body>
<button id="renderA">renderA</button>
<div id="root">
</div>
<hr/>
<div id="root1">
</div>
<template id="template1">
<h2>Flower ${M.a}</h2>
</template>
</body>
</html>
<script>
M={}
M.a=3;
//自带一个#root路由实例mingRouter
mingRouter.mapping('/a', "#template1",()=>{
console.log("AAAAA")
});
mingRouter.mapping('/b', "/b.html")
mingRouter.mapping('/c', `<div> AAAAAAAAA </div>`)
renderA.onclick=function(){
M.a++;
mingRouter.render()
}
//
//创建新的路由实例mingRouter1
mingRouter1 = new MingRouter("#root1");
mingRouter1.mapping('/a', `<div> AAAAAAAAA </div>`)
mingRouter1.mapping('/b', `<div> BBBBBBBBBBBBBBBB </div>`)
</script>
组件demo01
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="https://unpkg.com/ming_router/src/index.js"></script>
<title>Document</title>
</head>
<body>
<div id="root">
<user-card1 key="k1" onclick="changeImg()" id="c1Id" src="https://semantic-ui.com/images/avatar2/large/kristy.png"></user-card1>
<hr/>
<user-card2 type="1"></user-card2>
<hr/>
<user-card3></user-card3>
<hr/>
<user-card4/></user-card4>
<hr/>
<user-card5></user-card5>
</div>
</body>
</html>
<template id="usercard1Id">
#usercard1Id ${props.id}
<img width="100px" src="${props.src}">
</template>
<script>
//user-card1 用模板创建组件
MingRouter.registWebComponent(function UserCard1(props) {
return `#usercard1Id`
})
// user-card2 html文件 或字符串做模板
MingRouter.registWebComponent( function UserCard2(props) {
if(props.type==1){
return `<div>用返回做模板</div>`
}else {
return `./a.html`
}
})
// user-card3 用html文件做模板
MingRouter.registWebComponent(function UserCard3(props) {
return `./a.html`
})
// user-card4 类组件
MingRouter.registWebComponent(
class UserCard4 extends MingRouter.WebComponent {
render() {
return `<div>我是类组件</div>`;
}
});
// user-card5 类组件
MingRouter.registWebComponent(
class UserCard5 extends MingRouter.WebComponent {
static template= `#usercard1Id`;
});
//修改#c1Id的图片
function changeImg(){
document.querySelector("#c1Id").setAttribute("src","aa")
}
</script>
组件demo02
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="https://unpkg.com/ming_router/src/index.js"></script>
<title>Document</title>
</head>
<body>
<div id="root">
</div>
</body>
</html>
<!--mingRouter路由的模板-->
<template id="rootTemplateId">
<h1>ming_router.js demo</h1>
<hr/>
<!--如果组件有多个实例则必须有唯一的属性key-->
<user-card id="userCardId" key="name11" image="https://semantic-ui.com/images/avatar2/large/kristy.png" onclick1="MingRouter.Page.user.methods.ok">
<h2 slot="name">name11</h2>
<span slot="role">Developer</span>
<div slot="description" >
I hate Internet Explorer
<button onclick="alert(33)">插槽的按钮</button>
</div>
</user-card>
<hr/>
<user-card id="userCardId" key="name12" image="https://semantic-ui.com/images/avatar2/large/kristy.png" onclick1="MingRouter.Page.user.methods.ok">
<h2 slot="name">name12</h2>
</user-card>
</template>
<!--userCard组件的模板 属性用this.props.age , 方法用${this.selfName}.addAge -->
<!--状态用this.state.age -->
<template id="userCardTemplateId">
AAAAAAAAAAAAAAAAAA
<button onclick="${this.selfName}.addAge()">组件的按钮</button>
${this.state.age}
<img id="myImgId" src="${this.props.image}">
<div>
<slot name="name"></slot>
<slot name="description"></slot>
<slot name="role"></slot>
</div>
</template>
<script>
//#root 的路由映射,mingRouter默认挂载#root
//通过 mingRouter1 = new MingRouter("#root1"); 创建新的路由
mingRouter.mapping("/","#rootTemplateId");
mingRouter.mapping("/a","#rootTemplateId")
mingRouter.mapping("/b","<h1>BBBBBBBBBBB<h1/>")
</script>
<script>
//自定他组件,每个UserCard会根据组件的key,生成唯一一个实例名this.selfName
//MingRouter.componentMap.UserCard[key]
class UserCard extends MingRouter.WebComponent {
//https://www.yuque.com/docs/share/a87701d4-bb25-4d0a-b42d-225f834f9b7c?#C4kMr
static template="#userCardTemplateId";
constructor(props) {
super(props);
this.props = props;
}
//组件私有状态, this.setState({}) 会触发组件重新render
state = {
age: 1,
hobby:"111"
}
//组件方法,模板中, 通过${this.selfName}.addAge() 使用
addAge() {
//console.log(44);
let age = this.state.age + 1;
this.setState({ age })
}
//发出ajax
componentDidMount() {
console.log("....componentDidMount..selfName==>", this.selfName)
}
/**
* 通过
* document.querySelector("#userCardId").setAttribute("image","https://sem.png")
* 将触发此方法
*/
componentWillReceiveProps(props) {
console.log("....componentWillReceiveProps",props);
//属性发生变化,重新渲染
this.setState({})
}
/**
* 返回组件样式,建议用全局样式,否则有样式重复问题
*/
renderCss(){
return `
img{ width:20vw}
`
}
/**
* render优先级最低
*/
// render(props) {
// return `
// <button οnclick="${this.selfName}.addAge()"> aa </button>
// <div> ${props.image} </div>
// <div>age: ${this.state.age} </div>
// <div>hobby: ${this.state.hobby} </div>
// `;
// }
}
// 将UserCard注册为全局组件
MingRouter.registWebComponent(UserCard)
</script>
pageDemo
<style>
body{
color: red;
}
</style>
<div>
<h1>
role ${pageObj.data.count}
<button onclick="MingRouter.Page.role.methods.addCount()">+</button>
</h1>
<div>
${pageObj.data.todos.map(u=>{
return `<li>AAAAAAAAAAAAAAA==> ${u.text}</li>`
}).join("")}
</div>
</div>
/
const {Page}=MingRouter;
export default await Page({
name: "role",
async mounted() {
console.log(44)
let list= await MIO.todoList();
pageObj.data.todos=list;
},
data: {
count:4,
todos: [{text: '学习 JavaScript'}]
},
methods:{
addCount(){
pageObj.data.count++;
mingRouter.render();
}
}
})
页面模板或组件模板的获取规则
从指定的url中获取
这个url也可以是一个公网地址,前提是支持跨域,并且地址是.html结尾(后面不能含空格)
页面
mingRouter.mapping('/b', "template/b.html")
组件
class UserCard extends WebComponent {
static template="template/b.html"
}
从template标签获取
页面
mingRouter.mapping('/a', "#template1",()=>{
console.log("load success")
});
组件
class UserCard extends WebComponent {
static template="#template1"
}
从指定字符串中获取
如果router.mapping第二个参数不是#开头,也不是html结尾,则从传入的字符串中获取
页面
mingRouter.mapping('/c', `<div> AAAAAAAAA </div>`)
组件
class UserCard extends WebComponent {
static template=`<div> AAAAAAAAA </div>`
}
从render() 方法获取
组件除了上面三种方式获取组件模板外还可以从组件实例的render方法中获取, 从字符串获取模板的缺点是不能带变量,从 render 获取可以像从html获取那样带变量,从render获取的好处是真正实现单文件组件
路由映射router.mapping
一个MingRouter实例对应一个挂载节点,可同时创建多个MingRouter实例,有一个默认实例mingRouter挂载在#root上,MingRouter 构造方法有两个参数,第一个参数就是要挂载的节点,第二个参数是改节点是否使用模板字符串处理, 语法参考 ming_node app.use过滤器
默认为false
mingRouter = new MingRouter("#root",true)
mingRouter.mapping(path,模板来源,挂载完成后的钩子)
页面上下文
window.pageObj 存储了页面上下文信息
页面生命周期
页面只有一个mounted 函数,可通过变量配合route.refresh(),route.render()更新页面
组件的文件组成
简单的组件直接写一个js文件即可,结构样式全写在js里,简单组件其实也没必要封装成组件,复杂组件应将将结构样式分离出去,,比如写一个快递组件,需要写两个文件 ming-kuaidi.html(包含结构与样式), ming-kuaidi.js (包含组件状态与方法),在使用时只需引入ming-kuaidi.js即可,无需感知ming-kuaidi.html的存在[https://jsrun.net/AYUKp](https://jsrun.net/AYUKp)
组件tagName生成规则
组件tagName由驼峰类名或函数名转换为中划线而来
UserCard => user-card
MingRouter=> ming-router
获取组件实例与数据方法绑定
MingInput 是一个自定义组件的类,ming-input是对应的tag
对象 | 组件外部 | 组件内部 |
---|---|---|
wrapWebComponent的类 | MingRouter.componentMap[“MingInput”] |
或
MingInput | 模板中
${CurWebComponent}
类中
MingInput |
| shadowRoot | document.querySelector(“ming-input”).shadowRoot | this.shadowRoot |
| htmlElement实例 | document.querySelector(“ming-input”) | this.htmlElement |
| wrapWebComponent实例 | document.querySelector(“ming-input”).wrapWebComponent | this |
| 组件内部元素 | document.querySelector(“ming-input”).shadowRoot.querySelector(’#tanmuInputId’) | this.shadowRoot.
querySelector(’#tanmuInputId’) |
| wrapWebComponent 的send方法 | document.querySelector(“ming-input”).wrapWebComponent.send() | this.send() |
| 接收外部传入的方法 |
| let value=
this.shadowRoot.
querySelector(’#tanmuInputId’).
value;
eval(${this.props.onsend}(value)
); |
| 接收外部参数 | | this.props.onsend |
| 绑定组件内的方法 | | #组件模板中
酷金 |
| 绑定组件外的方法 | | #组件模板中
${this.state.selectData}
${this.props.name}
| | 组件样式0 | | #组件模板中
#组件类中
renderCss() {
return (div{ color:blue }
)
}
|
| 组件样式1
:host 外部传入类名影响组件样式 |
| :host{
color: green;
}
:host(.font-red){
color: red;
} |
| 组件样式2
::part 外部定义内部元素样式 | ming-input::part(h1-part){
color: pink;
} |
${this.state.selectData}
| | 组件样式3
通过css变量传入组件 | //全局css变量
:root{
–h1-color: green;
}
或
//组件局部css变量
ming-input{
–h1-color: green;
} | //不传默认红色
h1{
color: var(–h1-color,red);
}
|
| 组件内部样式获取 | getComputedStyle(
document.querySelector
(“ming-input”).
shadowRoot.querySelector(’#tanmuInputId’)
).backgroundColor | getComputedStyle(
this.shadowRoot.querySelector("#tanmuInputId")
).backgroundColor |
组件属性
组件属性必须全部为小写
组件生命周期方法
mingRouter的组件是react风格的,实现了如下生命周期函数,函数组件无生命周期,只能外部调用dom.
setAttribute后更新,WebComponent生命周期用绿色表示,HTMLElement的生命周期用蓝色表示,
两者有重合的部分,mingRouter自定义组件使用时只能重写绿色部分的部分的方法
render | 会直接替换整个dom树 |
---|---|
componentWillUnmount | 在组件从 DOM 中移除之前立刻被调用 |
componentWillReceiveProps | 外部调用组件的setAttribute()后调用 |
componentDidMount | 组件装载完成时调用 |
setState | 调用后会重新渲染组件 |
setAttribute | 外部手动修改属性,默认调用componentWillReceiveProps |
attributeChangedCallback | observedAttributes的属性发生变化会被调用 |
static get observedAttributes() | 声明要监听变化的属性,变化后会调用attributeChangedCallback,默认为[] |
constructor | 构造方法 |
adoptedCallback | 被移动到新的文档中时调用 |
disconnectedCallback | 元素从文档中被移除调用,默认调用组件的 componentWillUnmount |
connectedCallback | 元素首次被插入文档调用, 不可重写 |
模板字符串
当 new MingRouter("#root",true) 第二个参数为true时,则使用模板字符串,用的是es6模板字符串
模板字符串可使用当前上下文中的所有变量,参考[各种模板字符串](https://www.yuque.com/docs/share/f6a22b7c-60d1-46b1-9ca4-0193d73ab561?# 《模板字符串》)
<template id="template1">
<div>
aaa ${M.a}
<button>触发</button>
</div>
</template>
路由切换与刷新
替换切换
不会修改路由栈,不能返回上一步
MingRouter.replaceHash("/b")
原生切换
相当于压栈操作,可返回上一页
location.hash="#/b";
路由刷新
在切换新的路由时本身就是刷新,刷新当前路由使用如下方法
mingRouter.refresh()
不改地址栏的刷新
不传path则刷新当前路由
mingRouter.render([path])
原生方案
https://gitee.com/minglie/ming_node_router/tree/master/ming_router_nobuild
目录结构
![image.png](https://img-blog.csdnimg.cn/img_convert/edd3d0f68d19ba6b09f4eda0c3dc019d.png#clientId=ufe290e31-d1c1-4&from=paste&height=461&id=u82f5a156&margin=[object Object]&name=image.png&originHeight=922&originWidth=1083&originalType=binary&ratio=1&size=161497&status=done&style=none&taskId=u83694294-92ff-4bc8-ba58-131813f0180&width=541.5)
页面
,与vue类似,页面js导出时要加个name,目的是找对应的模板以及路由配置
export default await Page({
name: "role",
async mounted() {
console.log("userMounted")
}
})
注意事项
P1: 页面的css.html可写在到html中
P2: 可通过Page.页面名拿到页面实例
P3: 导出实例必须有name属性
P4: 修改data无法刷新需要手动调用操作dom 或 mingRouter.refresh() 刷新页面
组件
组件使用原生的webComponent,我用类似react风格进行了封装,组件应该为单文件
class UserCard extends MingRouter.WebComponent {
static className = "UserCard";
static tagName = "user-card";
constructor(props) {
super(props);
this.props = props;
console.log(this.constructor.name, "eee")
}
state = {
age: 1,
hobby:"111"
}
addCount() {
console.log(44);
let age = this.state.age + 1;
this.setState({ age })
}
renderCss() {
return (`
div{
color:blue
}
`)
}
componentDidMount() {
console.log("....componentDidMount")
}
render(props) {
return `
<button οnclick="${this.selfName}.addCount()"> aa </button>
<div> ${props.image} </div>
<div>age: ${this.state.age} </div>
<div>hobby: ${this.state.hobby} </div>
`;
}
}
MingRouter.registWebComponent(UserCard)
P1:组件传入时要加个key
<user-card key="name1" image="https://semantic-ui.com/images/avatar2/large/kristy.png" onclick1="Page.user.methods.ok()"></user-card>
P2: 组件的模板与样式应全部写到组件js中,要保证只需引入组件js就可直接使用
P3: 组件直接注册未全局组件,而不是导出
P4: 组件声明完需要在import.js中显示的导入
接口请求
用浏览器自带的fetch请求,实现的[数据请求规范](https://www.yuque.com/docs/share/a76a0bcf-f204-4d68-8f7d-7db247b5f9a7?#k9C7y),数据请求应该经过[ming_mock_vue](https://gitee.com/minglie/ming_node_router/blob/master/vue_nobuild/lib/ming_mock/ming_mock_vue.js) 周转一下, 用
MIO.userList({})进行调用。
app.get("/userList", async (req, res) => {
let r= await M.request.get("/api/userList")
res.send(r)
})
vue方案
https://gitee.com/minglie/ming_node_router/tree/master/vue_nobuild
目录结构
![image.png](https://img-blog.csdnimg.cn/img_convert/659eae994c9f02e146f5be70854abf55.png#clientId=u2e4745d2-9b63-4&from=paste&height=404&id=ub2f595e3&margin=[object Object]&name=image.png&originHeight=807&originWidth=1203&originalType=binary&ratio=1&size=157310&status=done&style=none&taskId=u4c8ba059-21f1-4611-a60b-5d194a34aa0&width=601.5)
页面
无法使用vue文件,而是把页面拆为,css,js,html三个文件,注意事项
P1: js中导出vue实例时要加一个name属性,方便ming_mock_vue找到对应的文件
P2:导出的实例需要使用 await Page() 方法包裹
P3: 如果不用html文件模板,则要显示的声明template属性
P4: 如果不用css文件样式,则要显示的声明beforeCreate 方法
![image.png](https://img-blog.csdnimg.cn/img_convert/87aa48d6c0e1795a1cc5ebd922db5ef0.png#clientId=u2e4745d2-9b63-4&from=paste&height=264&id=u167b7903&margin=[object Object]&name=image.png&originHeight=464&originWidth=881&originalType=binary&ratio=1&size=51221&status=done&style=none&taskId=uc13c6e17-f374-418e-b986-dd087966d18&width=501.5)
组件
这里组件与页面很类似,但有如下区别
P1: 组件直接注册未全局组件,而不是导出
P2: 组件对象需要用 await WrapComment() 方法包裹
P3: 组件无法导入css样式文件,为了方式组件样式污染重复,应用行内样式与全局样式代替
P4: 组件声明完需要在import.js中显示的导入
import.js
import "../../component/mi-img/mi-img.js"
![image.png](https://img-blog.csdnimg.cn/img_convert/41d52a8602cdc46ca2fa999f5b7a8e37.png#clientId=u2e4745d2-9b63-4&from=paste&height=232&id=ue4a63596&margin=[object Object]&name=image.png&originHeight=463&originWidth=727&originalType=binary&ratio=1&size=35253&status=done&style=none&taskId=u883f409b-9668-4f84-81c6-56a02f0f7da&width=363.5)
接口请求
用浏览器自带的fetch请求,实现的[数据请求规范](https://www.yuque.com/docs/share/a76a0bcf-f204-4d68-8f7d-7db247b5f9a7?#k9C7y),数据请求应该经过[ming_mock_vue](https://gitee.com/minglie/ming_node_router/blob/master/vue_nobuild/lib/ming_mock/ming_mock_vue.js) 周转一下, 用
MIO.userList({})进行调用。
app.get("/userList", async (req, res) => {
let r= await M.request.get("/api/userList")
res.send(r)
})
一些ming_router写的移动端组件
输入框
弹幕
ming_router待新增功能
待新增功能
参考demo
各种模板字符串
https://gitee.com/minglie/ming_node_router
](https://github.com/zxuqian/html-css-examples/tree/master/31-webcomponent-get-started)