钩子(hook)
钩子的概念源于Windows的消息处理机制,通过设置钩子,应用程序可以对所有的消息事件进行拦截,然后执行钩子函数,对消息进行想要的处理方式。比如截获键盘、鼠标的输入,屏幕取词,日志监视等等,钩子实际上是一个处理消息的程序段,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权
钩子机制也叫hook机制,或者你可以把它理解成一种匹配机制,就是我们在代码中设置一些钩子,然后程序执行时自动去匹配这些钩子
在javascript中钩子是将需要执行的函数或者其他一系列动作注册到一个统一的入口,程序通过调用这个钩子来执行这些已经注册的函数,还有就是提供一个可以响应默认流程的机制的时机,像react中生命周期的componentWillMount,componentDidMount等,这些都是钩子函数。
简而言之,钩子函数就是拥有特殊匹配规则的回调函数
为什么要使用钩子?怎样去衡量该不该使用钩子?
当某一个任务有一个默认的响应流程的时候就可以给该任务加入钩子
一个钩子的基本结构
钩子的构成遵循发布订阅的设计模式
1.定义钩子函数
2.绑定钩子函数
3.卸载钩子函数
let hooks = {
after_login: {
type: 'listener',
mulit: true,
listener: []
},
...
}
function bindHook(name, listener) {
if (!name) throw new Error('缺少hookname');
if (name in hooks === false) {
throw new Error('不存在的hookname');
}
if (hooks[name].mulit === true) {
hooks[name].listener.push(listener);
} else {
hooks[name].listener = listener;
}
}
function emitHook(name, ...args) {
if (!hooks[name]) throw new Error('不存在的hook name');
let hook = hooks[name];
if (hook.mulit === true && hook.type === 'listener') {
if (Array.isArray(hook.listener)) {
let promiseAll = hook.listener.map(item => Promise.resolve(item.call(pluginModule, ...args)));
return Promise.all(promiseAll);
}
} else if (hook.mulit === false && hook.type === 'listener') {
if (typeof hook.listener === 'function') {
return Promise.resolve(hook.listener.call(pluginModule, ...args));
}
}
}
pluginModule = {
hooks: hooks,
bindHook: bindHook,
emitHook: emitHook
}
钩子的具体使用场景
生命周期(lifecycle):init -> created -> compiled -> ......
生命周期中每个周期再单独定义一个小周期(extend):beforeInit -> init -> inited; beforeCompile -> compile -> compiled
数据请求: beforeRequest(参数处理,headers处理) -> afterRequested(返回参数处理)
路由切换: router.beforeEach -> router.afterEach
富文本编辑器:例如保存,新建,导入导出
懒加载
Vue里面钩子的应用
一、路由钩子
路由钩子主要是给使用者在路由发生变化时进行一些特殊的处理而定义的
1.全局钩子
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
next()
})
2.路由独享的钩子
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
3.组件内的钩子
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
二、Vue生命周期hooks
vue ssr 新加入了asyncData钩子
理解vue实例创建过程(vue2.1.9+)
initLifecycle:生命周期变量初始化
initEvents:初始化events,绑定父组件传过来的事件到$listeners
initRender:渲染Vnode
initInjections:将父组件的provider注入当前的实例的injection中(vue2.2加入的新选项)
initState: 状态初始化data/props/methods/computed/watch
initProvide:初始化provide
vue(2.0.0 - 2.1.9)
三、$emit
四、指令directive
React里面的钩子
一、路由
onEnter和onLeave
withRouter中的setRouteLeaveHook
二、生命周期(版本16+)
getDerivedStateFromProps 组件每次被rerender的时候,包括在组件构建之后
getSnapshotBeforeUpdate 组件更新前触发
shouldComponentUpdate 组件Props或者state改变时触发
componentDidMount 挂载后
componentDidUpdate 组件更新后触发
componentDidCatch(error,info) 获取到javascript错误
componentWillUnmount 组件卸载时触发
生命周期
根据百度百科,很好理解:
生:create
老:update
病:error
死:destroy
自己实现一个简单的生命周期
在生命周期中使用钩子需要注意实例的传递
var hooks = {
init: {
type: 'listener',
mulit: false,
listener: null
},
beforeCompile: {
type: 'listener',
mulit: false,
listener: null
},
compile: {
type: 'listener',
mulit: false,
listener: null
},
compiled: {
type: 'listener',
mulit: false,
listener: null
},
created: {
type: 'listener',
mulit: false,
listener: null
}
}
function bindHook(name, listener) {
if (!name) throw new Error('缺少hookname');
if (name in hooks === false) {
throw new Error('不存在的hookname');
}
if (hooks[name].mulit === true) {
hooks[name].listener.push(listener);
} else {
hooks[name].listener = listener;
}
}
function emitHook(name, ...args) {
if (!hooks[name]) throw new Error('不存在的hook name');
let hook = hooks[name];
if (hook.mulit === true && hook.type === 'listener') {
if (Array.isArray(hook.listener)) {
let promiseAll = hook.listener.map(item => Promise.resolve(item.call(hookModule, ...args)));
return Promise.all(promiseAll);
}
} else if (hook.mulit === false && hook.type === 'listener') {
if (typeof hook.listener === 'function') {
return Promise.resolve(hook.listener.call(hookModule, ...args));
}
}
}
let $instance = 0
hookModule = {
hooks,
$instance,
bindHook,
emitHook
}
bindHook('init', function() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('init')
this.$instance++
resolve('resolve')
}, 2000)
})
})
bindHook('beforeCompile', function() {
return new Promise((resolve, reject) => {
setTimeout(() => {
this.$instance++
console.log('beforeCompile')
resolve('beforeComile')
}, 1000)
})
})
bindHook('compile', function() {
return new Promise((resolve, reject) => {
emitHook('beforeCompile')
setTimeout(() => {
console.log('compile')
emitHook('compiled')
this.$instance++
resolve('compiled')
}, 1000)
})
})
bindHook('compiled', function() {
return new Promise((resolve, reject) => {
setTimeout(() => {
this.$instance++
console.log('compiled')
resolve('compiled')
}, 1000)
})
})
bindHook('created', function() {
return new Promise((resolve, reject) => {
setTimeout(() => {
this.$instance++
console.log('created')
resolve('created')
}, 1000)
})
})
emitHook('init').then(() => emitHook('compile').then())
// hookModule.$instance === 4
切记不可使用箭头函数,箭头函数绑定了父上下文,实例无法通过this传入函数中