介绍
语缺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;
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 = 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>
MingRouter.registWebComponent(function UserCard1(props) {
return `#usercard1Id`
})
MingRouter.registWebComponent( function UserCard2(props) {
if(props.type==1){
return `<div>用返回做模板</div>`
}else {
return `./a.html`
}
})
MingRouter.registWebComponent(function UserCard3(props) {
return `./a.html`
})
MingRouter.registWebComponent(
class UserCard4 extends MingRouter.WebComponent {
render() {
return `<div>我是类组件</div>`;
}
});
MingRouter.registWebComponent(
class UserCard5 extends MingRouter.WebComponent {
static template= `#usercard1Id`;
});
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>
mingRouter.mapping("/","#rootTemplateId");
mingRouter.mapping("/a","#rootTemplateId")
mingRouter.mapping("/b","<h1>BBBBBBBBBBB<h1/>")
</script>
<script>
class UserCard extends MingRouter.WebComponent {
static template="#userCardTemplateId";
constructor(props) {
super(props);
this.props = props;
}
state = {
age: 1,
hobby:"111"
}
addAge() {
let age = this.state.age + 1;
this.setState({ age })
}
componentDidMount() {
console.log("....componentDidMount..selfName==>", this.selfName)
}
componentWillReceiveProps(props) {
console.log("....componentWillReceiveProps",props);
this.setState({})
}
renderCss(){
return `
img{ width:20vw}
`
}
}
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写的移动端组件
输入框
https://jsrun.net/VyUKp
弹幕
https://jsrun.net/3yUKp
ming_router待新增功能
待新增功能 ?
参考demo
各种模板字符串 ?
https://gitee.com/minglie/ming_node_router ?
ming_mock.js ?
ming_router.js
webcomponent-demo [
](https://github.com/zxuqian/html-css-examples/tree/master/31-webcomponent-get-started)
|