(以下tode是已经实现的内容,文档还未写)
vact,从名字就能看出,和vue还有react是少不了关系的,它是一款基于vue的响应式原理,以及react的jsx语法,并结合我自身的创新开发而来。当然,仅凭我一个人是无法将它维护的很好的,所以,我想借助它来向大家展示一下我自己的想法以及一些有趣的用法,那么接下来,请听我娓娓道来。
这套框架的当然是具备声明式渲染和响应性的特点的,但不同的是,它并不是以比较diff为主,而是在一开始就确认好每一个活跃的dom节点的响应性,只有在一些比较严苛的条件(比如数组),才会启用diff进行比较。如果你不理解的话可以先接着往下看,这不影响你接下来的阅读。
注:下面的实例都是基于jsx语法,因为使用创建节点的api过于繁琐
import { state, createApp } from 'vactapp'
const count = state(0)
let app = <div>
<button onClick={() => count.value++}>
count is:{count.value}
</button>
</div>
createApp(app).mount('#app')
结果如下:
可以通过将节点挂载到真实dom来实现部分响应式代理
如果使用创建节点的api去写的话会比较麻烦,目前推荐使用jsx并安装我写的babel解析插件
|推荐直接在demo中运行!!!|(看下面)
babel预设:
@babel/preset-env
babel插件:
babel-plugin-syntax-jsx
(识别jsx语法)
babel-plugin-transform-vact-jsx
(翻译vact的babel)
- master分支中demo文件中的demo1目录是简单的脚手架环境
- master分支中demo文件中的demo2目录是完善的webapck脚手架环境,可以解析包括其他各类资源
运行demo:npm i yarn -g (如果有yarn请跳过)
yarn install
yarn dev
import { createApp } from 'vactapp'
const app = createApp(<div>Hello World!</div>)
app.mount('#app')
这部分其实不需要我过多介绍,现在非常流行,而且网上也有教程
简单写一下
- html代码块中大括号里面用来写表达式
const template = <div id={'app'}>
{'hello'}
</div>
- 事件以on开头,且代码块中为函数且只能为函数
const button = <button onClick={(e) => console.log(e.target)}>确定</button>
想要实现响应式,必须首先创建响应式对象
import { defineState, watch } from 'vactapp'
const data = defineState({
count: 0
})
watch(() => data.count, (oldValue, newValue) => {
console.log(oldValue, newValue)
})
data.count++ // 打印 0, 1
当然也可以创建基础属性的响应对象,但数值必须通过value访问
import { state } from 'vactapp'
const count = state(0)
watch(() => count.value, (oldValue, newValue) => {
console.log(oldValue, newValue)
})
count.value++ // 打印 0, 1
style可以为字符串或者对象,如果为对象则prop为属性,value为值,className目前只能写字符串,但两者都可为响应式
const color = state('red')
const app = createApp(
<div style={{ color: color.value }}>
Hello World!
</div>
).mount('#app')
// or const app = createApp( <div style={`color: ${color.value};`}>Hello World!</div>)
setTimeout(() => color.value = 'blue', 3000)
className的响应同style的第二个
不仅是style和className,任何引用响应式变量的属性都会变为响应式
子元素在代码块中且引用响应式变量即可实现响应式
const show = state(true)
const app = createApp(
<div>
{show.value && 'Hello World!'}
</div>
).mount('#app')
setInterval(() => show.value = !show.value, 1000)
上一个例子中我们展示了单对象条件渲染
我们再展示一个双对象条件渲染
const show = state(true)
const app = createApp(
<div>
{show.value ? <span>Hello</span> : <span>World</span> }
</div>
).mount('#app')
setInterval(() => show.value = !show.value, 1000)
多对象条件渲染
function App() {
let data = defineState({
show: 1,
})
let item = () => {
if (data.show === 1) {
return <span>条件1</span>
} else if (data.show === 2) {
return <span>条件2</span>
} else {
return null
}
}
return <div>
{item()}
<button onClick={() => data.show = ++data.show % 3}>切换</button>
</div>
}
createApp(<App />).mount('#app')
如果你已经看完了组件部分,你甚至可以封装一个v-if
class HelloWorld{
render() {
return () => this.props['v-if'] ? this.component() : null
}
component() {
return <div>hhhh</div>
}
}
let show = state(true)
function App() {
return <div>
<HelloWorld v-if={show.value} ></HelloWorld>
<button onClick={() => show.value = !show.value}>切换</button>
</div>
}
createApp(<App />).mount('#app')
你甚至可以写一个根组件,用于给任意组件提供这个能力
// 封装一个提供v-if能力的组件
class VIF {
render() {
return () => this.props['v-if'] ? this.component() : null
}
}
class HelloWorld extends VIF {
component() {
return <div>hhhh</div>
}
}
let show = state(true)
function App() {
return <div>
<HelloWorld v-if={show.value} ></HelloWorld>
<button onClick={() => show.value = !show.value}>切换</button>
</div>
}
createApp(<App />).mount('#app')
怎么样,抛开性能不谈,是不是很有趣,有时候我们可以为了有趣去学习,而不是烦恼患得患失
如果我们将一个数组返回到代码块,那么它将会把这个数组整体作为一个数组节点渲染到页面
function App() {
const data = defineState({
list: [1, 2, 3, 4, 5]
})
return <div>
<ul>
{data.list.map(num => <li>{num}</li>)}
</ul>
<button onClick={() => data.list = [2, 3, 4, 1, 5]}>修改</button>
<button onClick={() => data.list.push(data.list.length + 1)}>增加</button>
</div>
}
目前对于数组节点的渲染更新默认是没有优化的,不过你可以在配置项开启优化,该优化会启用diff算法进行比较更新,复用节点,目前还在实验,可能会有bug
createApp(<App />, { arrayDiff: true }).mount('#app')
事件都是以on开头,传入一个函数即可,但是事件可以作为props传入组件内部使用
function Input(props) {
return <div>
<input type="text" onInput={(e) => props.onChange(e.target.value)} />
</div>
}
function App() {
const text = state('')
return <div>
{text.value}
<Input value={text.value} onChange={(newText) => text.value = newText} />
</div>
}
createApp(<App />).mount('#app')
怎么样,是不是很熟悉,没错就是v-model双向绑定,而且非常简单灵活,有趣吧
上面那个例子就生动形象的体现了表单绑定,这里我就不再举例了
我们可以监听任意个响应式变量来设置一个回调函数
监听一个
const data = defineState({
count: 0
})
watch(() => data.count, (oldValue, newValue) => {
console.log(oldValue, newValue)
})
data.count++
监听多个
const data = defineState({
count: 0,
count2: 1
})
watch(() => data.count + data.count2, (oldValue, newValue) => {
// 注意这里返回的是两者相加的值,而不是单单一个变量
console.log(oldValue, newValue)
})
data.count++
data.count2++
你可以通过这个在响应式变量变化后做一些额外的操作
你可以通过函数声明一个组件,如果是作为函数调用,会传入props和children,如果是作为构造函数,原型必须有一个render函数,props和children会绑定在this上
function Button(props, children) {
return <><button onClick={props.onChange}>{props.text}</button></>
}
const text = state('哈哈哈')
const app = <div><Button text={text.value} onChange={() => text.value = '123'}></Button></div>
createApp(app).mount('#app')
必须有一个render函数, 返回一个根节点(可以为fragment)
class App {
constructor() {
this.data = defineState({
count: 0
})
}
render() {
console.log(this.props, this.children)
return <div>
{this.data.count}
<Button onClick={() => this.data.count++} >增加</Button>
<Button2 onClick={() => this.data.count++} >增加</Button2>
</div>
}
}
你可以使用函数声明组件,返回需要渲染的html,同时属性和子元素会作为参数传入
组件属性如果是响应式的,那么它的响应性会传递到子组件,且你可以将props的值无限向下传递,仍然会保持它的响应
function Fun1(props) {
return <div>
fun1-value: {props.value}
<Fun2 value={props.value}></Fun2>
</div>
}
function Fun2(props) {
return <>
fun2-value: {props.value}
</>
}
function App() {
const value = state(0)
return <div>
<Fun1 value={value.value}></Fun1>
<button onClick={() => value.value++}>增加value</button>
</div>
}
插槽将作为children传入
function Text(props, children) {
return <>{children}</>
}
const show = state(true)
const app = <div>
<Text>
{show.value && <span>hello</span>}
<span>world</span>
</Text>
</div>
setInterval(() => {
show.value = !show.value
}, 1000);
createApp(app).mount('#app')
这边不太建议使用children来实现具名插槽,也不是不行
更推荐使用props实现
function Text(props, children) {
const slots = props.slots
return <>
<div>**{slots.head}**</div>
<div>**{slots.middle}**</div>
<div>**{slots.bottom}**</div>
</>
}
const $slots = {
head: <span>头部</span>,
middle: <span>中部</span>,
bottom: <span>底部</span>
}
const app = <div>
<Text slots={$slots}></Text>
<div>
<button onClick={() => {
console.log($slots.middle = null);
}}>中部消失</button>
</div>
</div>
createApp(app).mount('#app')
在条件渲染中我们已经展示过了动态组件,简单来说,动态组件返回的是一个函数,在这个函数中我们需要引用响应式变量并返回html模板
function Fun(props) {
return function () {
if (props.isHello) {
return <span>hello</span>
} else {
return <span>world</span>
}
}
}
function App() {
const isHello = state(true)
return <div>
<Fun isHello={isHello.value}></Fun>
<button onClick={() => isHello.value = !isHello.value}>切换</button>
</div>
}
让我们看看,Fun返回了一个函数,然后我们说过,props可以保存变量的响应性,所以我们就得到了一个动态组件
我们甚至可以封装一下动态组件,就像vue中的component组件一样
function Component(props) {
const components = props.components
return function () {
let Cur = components[props.is]
if (Cur) return <Cur></Cur>
else return null
}
}
const components = {
one: () => <div>one</div>,
two: () => <div>two</div>,
three: () => <div>three</div>
}
function App() {
const is = state('one')
return <div>
<Component is={is.value} components={components}></Component>
{Object.keys(components).map(name => <button onClick={() => is.value = name}>切换到{name}</button>)}
</div>
}
怎么样,有意思吧,如果你觉得有趣,不妨写一个demo试试
如果你有更好的想法或者建议,请随时call我,本人目前大二马上大三,菜比一枚,可以共同进步交流
qq:2480721346
如果未来有时间的话,我会完善一下框架的细节,或者写一个router,学习写一个脚手架之类的