组件一共分为两种,一种通过 class 语法定义一个类,为有状态的组件,另外一种为无状态组件,直接通过函数来定义,返回一段模板。
- class 组件 通过继承 Grass.Component
class P extend Grass.Component {
constructor (props) {
super(props)
}
template () {
return (`<div></div>`)
}
}
P.$mount(document.getElementById('xx'))
- 无状态组件
function P (props, register, parent) {
return (`<div></div>`)
}
Grass.mount(document.getElementById('xx'), P)
class 组件必须有一个 template 属性或方法,如果为一个方法,它应该返回一段模板代码,否则会抛出一个错误。模板代码应该只能有一个根元素
component 属性或方法用来注册子组件,它应该为一个对象或者是返回一个对象的方法
class P extend Grass.Component {
constructor (props) {
super(props)
this.component = { XXComponent: xx }
}
// or
component () {
return {
XXComponent: xx,
}
}
}
生命周期的回调只能在 class 形式的组件中使用
-
beforeCreate
此时组件刚刚实例化完成,此时的还未对组件进行实例化,所以,在这里可以定义组件的state
、component
、template
等属性,但是所有此时组件内部的属性都为初始值,例如$parent
、$children
等 -
created
在组件实例化之后,会调用此方法,此时所有的组件内部属性值已经与组件树联系起来,此函数接受一个参数,为组件挂载后的真实 dom 元素 -
willUpdate
在组件将要更新的时候调用,此函数接受一个参数,为当前组件真实的 dom 元素,与组件的$el
属性一致。this.$el === dom
-
willReceiveProps
在子组件将要更新的时候,会调用此函数,它将在willUpdate
之前调用,这个函数接受一个新的 props 参数。这个函数的主要是用来优化组件,避免不必要的渲染,return false
可以阻止当前组件的更新 -
didReceiveProps
当接收父组件 props 后,会调用此函数,所以可以在这里针对父组件的数据传递而做出对应的处理,而不必再didUpdate
函数里面做区分 -
didUpdate
在组件更新完成后调用,此函数接受一个参数,为当前组件真实的 dom 元素 -
destroy
在组件即将销毁的时候调用,此函数接受一个参数,为当前组件真实的 dom 元素。在此函数中可以对组件的一些事件绑定等进行解绑,避免内存的泄露
生命周期顺序
+------------------+
| beforeCreate |
+------------------+
|
v
+------------------+
| created |
+------------------+
|
v
+------------------+
| willReceiveProps |
+------------------+
|
v
+------------------+
| didReceiveProps |
+------------------+
|
v
+------------------+
| willUpdate |
+------------------+
|
v
+------------------+
| didUpdate |
+------------------+
|
v
+------------------+
| destroy |
+------------------+
- $mount
在 class 组件下,
$mount
这个 api 能够把当前组件挂载到一个真实的 dom 上,它是Grass.mount
的别名
class P extends Grass.Component {
beforeCreate () {
this.template = '<div></div>'
}
}
P.$mount(dom)
// Grass.mount(dom, P)
- createState
- createResponseState
- setState
- forceUpdate
- getComponent
- set(响应模式下)
- delete(响应模式下)
createState
能够创建一个纯碎的 state,他会丢弃 state 的原有原型链。(对于 state,很重要的一点是,我们需要一个原型链干净的 state,也就是原型链必须为 Object.prototype
或者 null
,否则会出现一些其他的问题)
class P extends Grass.Component {
beforeCreate () {
const state = Object.create({ a: 1})
state.b = 2
this.createState(state)
// state.a === undefined
}
}
createResponseState
能够创建一个响应式的 state,同样的,他会丢弃 state 原有的原型链
在非响应式的模式下(可以通过 $isWatch 查看当前组件的模式),可以通过 setState
来更改状态,更新视图,在响应式模式下,此方法会给出一个报错。状态的更改不会立即响应到视图,也不能立即得到更改的状态。它有两种使用方式,一种为传入 state,一种为回调函数形式,在回调函数中,你能够拿到最新的 state,这个回调需要返回一个 state
class P extends Grass.Component {
created () {
this.setState({ a: 1 })
console.log(state.a) // undefined
setTimeout(() => console.log(state.a)) // 1
}
}
回调函数的使用
class P extends Grass.Component {
created () {
this.setState({ a: 1 })
this.setState(state => {
console.log(state.a) // 1
return state
})
}
}
这个方法会强制更新当前组件一次
获取当前组件注册的子组件,如果注册的子组件为异步组件,你可能没有办法直接拿到子组件的构造函数,所以可以通过此方法来获取,它接受一个为 string 类型的参数,为子组件的注册名字
class P extends Grass.Component {
beforeCreate () {
this.component = {
Child: Grass.async(() => {
...
})
}
}
created () {
this.getComponent('Child')
}
}
在响应模式下,由于没有办法监视到 object 属性的增加和删除,所有你可以通过这个俩方法来新增和删除 object 的属性,并保证新加入的属性也是响应模式的。需要注意,在响应模式下才会有这两个方法
-
state 组件的 state
-
props 父组件传入的 props,它是响应式的,父组件更新,子组件所用到的 props 也会同步更新,当然,可以通过
willReceiveProps
来阻止更新 -
$el 组件 mount 后的真实 dom 元素
-
$slot 当前组件的 slot
-
$parent 当前组件的所在的父组件实例
-
$children 当前组件注册的子组件实例,通过 ref 来指定需要获取的子组件。需要注意的是,如果子组件是异步组件的形式,因为所以没有办法控制组件具体加载的时间,所以你可以在异步组件的钩子里面获取当前组件的子组件实例。
const one = () => '<div></div>'
class two extends Grass.Component {
component () {
return { one }
}
template () {
return `<div><one ref="child"/></div>`
}
created () {
console.log(this.$children.child) // one instance
}
}
- $isWatch 当前组件在哪种模式下
我们可以使用 slot 内置组件来进行一些插槽行为
- 父组件
<template>
<child>
<div slot="tao">{{api}}</div>
<bb slot='fang'></bb>
</child>
</template>
- 子组件
<template>
<slot name="tao"></slot>
<slot name="fang"></slot>
<slot></slot>
</template>
我们可以指定 name 属性来选取需要插入的插槽,当没有 name 属性的时候,会把所有内容都插入进来,而且我们还可在组件内通过 $slot
属性来拿到所有的插槽内容的 vnode
,插槽的内容都是在所定义的组件之内编译的,这是需要注意的一点
<template>
<div styleName="xx" @click="this.click.bind(this)">{{ tao }}</div>'
</template>
<script>
import { CSSModules } from 'grass'
import style from './style.css'
@CSSModules(style)
class P extends Grass.Component {
constructor () {
super()
this.createResponseState({
tao: 'taotao',
})
}
click () {
this.state.tao = 'tao'
}
}
</script>
响应数据模式和普通模式,最好不要混用
- props
- register
- parent
使用方法
function p (props, register, parent) {
return '<div></div>'
}
props
与 class 组件形式的 this.props
一样,都是父组件传下来的,不同的是,在无状态组件中,我们在模板中不需要使用 this.props.xx
,在无状态组件中,props
与 class 组件的 state
一样,可以简写
class 组件
class P extends Grass.Component {
constructor () {
this.state = { a : 1 }
// this.props = { b: 2 }
}
template () {
return '<div>{{ a }} {{ this.props.b }}</div>'
}
}
无状态组件
function p (props) {
// props -> { b : 2 }
return `<div>{{ b }}</div>`
}
当前,在无状态组件中,你可以对 props
进行更改,由于是引用模式,props
所有的更改都将映射到真实的视图中
function click (e) {
...
}
function p (props) {
props.c = click
return `<div @click="c"></div>`
}
register
可以让当前无状态组件注册子组件,不然在无状态组件中,你无法使用子组件,这是个很大的问题,也真是这个方法出现的原因。它接收一个参数或俩参数,返回函数自身
function one () {
return '<div>child</div>'
}
function two (props, register, parent) {
register('Child', one)
// 如果只传入组件,那么组件的 constructor name 将作为组件的名字
// register(one)
return '<div><Child/></div>'
}
parent
对应着 class 组件中的 this.$parent
属性