Svelte系列 --- 动作

764 阅读2分钟

动作(action),其本质是元素上的生命周期函数。它可用于譬如以下几个方面

  • 与第三方库对接(在组件上集成第三方库的功能)
  • 延迟加载图片
  • 工具提示(tooltips)
  • 添加自定义事件处理程序

`action 本质上只是一个普通的函数,它接收一个参数,就是当前元素的 DOM 节点对象

action 是一种为组件增强能力的特性,具有较强的封装性和复用性,可以将逻辑代码拆分到 JS 文件中供反复应用

// 一个action的定义 函数名任意
export function movable(node) {
  // ... 第三方库或自定义事件处理程序的代码

  return {
    destroy() {
      // ... destroy 函数会在元素被清除时调用,
      // 可以在此做一些清理工作
    }
  }
}
<script>
  import movable from './movable'
</script>

<!-- 通过 use 指令将自定义工具函数或第三方库应用到div上 -->
<div use:movable>Box</div>
<!-- 可以监听工具函数中的自定义事件 -->
<div
  use:movable
  on:movestart={e => console.log('move start...', e.detail)}
  on:moving={e => console.log('moving', e.detail)}
  on:moveend={() => console.log('move end...')}
>Box</div>
export default function movable(node) {
  let moving = false
  let x, y, left, top
	
  function handleMove(e) {
    if (moving) {
      node.style.left = (e.clientX - x + left) + 'px'
      node.style.top = (e.clientY - y + top) + 'px'
			
      // 发送moving事件
      // 使用node.dispatchEvent来发送对应的事件
      // new CustomEvent(type, option) 是JS原生提供的创建自定义事件的方式
			node.dispatchEvent(new CustomEvent('moving', {
        detail: { x: e.clientX - x + left, y: e.clientY - y + top }
      }))
    }
  }

  function startMove(e) {
    moving = true
		
    x = e.clientX
    y = e.clientY
		
    left = parseInt(node.style.left) || 0
    top = parseInt(node.style.top) || 0
		
    // 发送movestart事件
    node.dispatchEvent(new CustomEvent('movestart', { detail: {x:left, y:top}}))
  }
	
  function endMove() {
    moving = false
		
    // 发送moveend事件
    node.dispatchEvent(new CustomEvent('moveend'))
  }
	
  window.addEventListener('mousemove', handleMove)
  window.addEventListener('mouseup', endMove)
  node.addEventListener('mousedown', startMove)
	
  return {
    destroy() {
      window.removeEventListener('mousemove', handleMove)
      window.removeEventListener('mouseup', endMove)
      node.removeEventListener('mousedown', startMove)
    }
  }
}

附加参数

正如 transition 过渡效果和 animate 动画一样,动作(Action)也支持附带参数,动作函数会与它所在的元素一起被调用。

longpress.js

// action函数接收2个参数
// 参数1:绑定action的节点对象
// 参数2:绑定action的元素在使用的时候传入的参数
export function longpress(node, duration) {
  let timer;
	
  const handleMousedown = () => {
    timer = setTimeout(() => {
      node.dispatchEvent(
        new CustomEvent('longpress')
      );
    }, duration * 1000);
  };
	
  const handleMouseup = () => clearTimeout(timer);

  node.addEventListener('mousedown', handleMousedown);
  node.addEventListener('mouseup', handleMouseup);

  return {
    destroy() {
      node.removeEventListener('mousedown', handleMousedown);
      node.removeEventListener('mouseup', handleMouseup);
    }
  };
}
<button
  use:longpress={duration}
  ...
>按住我别放</button>
<!-- 
	在action函数中,因为只有一个参数来接收外部传入的参数内容
	所以如果需要传入多个参数的时候,只能将它们组合成一个对象来进行统一传入
-->
<button
  use:longpress={{duration, spiciness}}
  ...
>按住我别放</button>
return {
  destroy() {
    node.removeEventListener('mousedown', handleMousedown);
    node.removeEventListener('mouseup', handleMouseup);
  },
  // action对应的函数会在组件初始化的时候被执行
  // 这意味着后续如果传入的参数的值发生了改变的时候,action对应函数是无法知晓的
  // 此时可以在action的返回对象中增加update方法
  // 每当参数有变,会立即调用update方法,并将参数的最新值作为参数传入
  update(newDuration) {
    duration = newDuration;
  }
};