Rea.js 一个适合用于写原生 demo 的极简响应式 js 框架

项目地址:点击图片

  • 隐藏 DOM 操作,简化写原生 HTML/CSS/JavaScript Demo 操作

  • 使用了 Proxy Map 箭头函数,所以需要浏览器支持 ES2015

  • rea.js 代码不到100行,压缩后 rea.min.js 仅 1.36 KB

  • 实现原理是借助 HTML 属性 和 JavaScript Proxy 实现侦听数据变化操作 DOM

使用

html 复制代码
<script src="https://baoanj.github.io/rea.js/rea@0.0.1.min.js"></script>

使用方式和 Vue 比较类似。

指令(HTML 属性)支持:

  • r-变量
  • r-show-变量
  • r-model-变量
  • r-for-变量
    • r-prop-属性
  • r-event="事件:方法"

配置(options)支持:

  • data
  • computed
  • methods
html 复制代码
<div>
  <input type="text" placeholder="开启你的待办事项" maxlength="20" r-model-todoName>
  <span r-show-isShowLimit r-todoNameLimit></span>
  <select r-model-todoType r-for-typeList>
    <option value="r-prop-value">r-prop-label</option>
  </select>
  <input type="datetime-local" r-model-todoTime>
  <button r-event="click:addTodo">添加待办</button>
  <span r-show-todoTip r-todoTip></span>
</div>
<div r-for-todoListFormat>
  <div class="todo-item todo-item-r-prop-check">
    <div r-event="click:checkTodo" class="todo-item-check todo-item-check-r-prop-check"></div>
    <span>r-prop-name</span>
    <span>r-prop-type</span>
    <span>r-prop-time</span>
    <button r-event="click:delTodo">删除</button>
  </div>
</div>

<script src="https://baoanj.github.io/rea.js/rea@0.0.1.min.js"></script>
<script>
  Rea({
    data: {},
    computed: {},
    methods: {}
  })
</script>

options

data

一个对象,放置数据属性。

    • 若数据属性的值为数组,可响应数组变更方法:push pop unshift shift splice sort reverse 索引赋值
    • 若数据属性的值为对象,暂不支持响应对象内属性值的变化
js 复制代码
{
  typeList: [
    { label: '高', value: 'h' },
    { label: '中', value: 'm' },
    { label: '低', value: 'l' }
  ],
  todoName: '',
  todoType: null,
  todoTime: null,
  todoList: [
    { name: 'Todo Example 1', type: 'h', time: '2023-08-12T15:18', check: false },
    { name: 'Todo Example 2', type: 'm', time: '2023-08-12T15:18', check: true }
  ],
  todoTip: ''
}

computed

一个对象,每个计算属性是一个函数,this 为当前实例,可访问当前实例的数据属性、计算属性、方法属性。

js 复制代码
{
  isShowLimit() {
    return this.todoName.length > 0
  },
  todoNameLimit() {
    return this.todoName.length + '/20'
  },
  todoListFormat() {
    return this.todoList.map(m => {
      return {
        name: m.name,
        type: this.typeList.find(f => f.value === m.type).label,
        time: m.time.replace('T', ' '),
        check: m.check
      }
    })
  }
}

methods

一个对象,每个方法属性是一个函数,this 为当前实例,可访问当前实例的数据属性、计算属性、方法属性。

js 复制代码
{
  addTodo() {
    if (!this.todoName) {
      this.showTip('请填写待办名称')
      return
    }
    if (!this.todoType) {
      this.showTip('请选择优先级')
      return
    }
    if (!this.todoTime) {
      this.showTip('请选择待办时间')
      return
    }
    this.todoList.unshift({
      name: this.todoName,
      type: this.todoType,
      time: this.todoTime,
      check: false
    })
    this.todoName = ''
    this.todoType = null
    this.todoTime = null
  },
  showTip(msg) {
    this.todoTip = msg
    setTimeout(() => {
      this.todoTip = ''
    }, 3000)
  },
  delTodo(event, index) {
    this.todoList.splice(index, 1)
  },
  checkTodo(event, index) {
    this.todoList[index] = Object.assign(this.todoList[index], {
      check: !this.todoList[index].check
    })
  }
}

指令(HTML 属性)

r-变量

响应变量值的变化,将变量值写到元素的 textContent 。

html 复制代码
<span r-todoNameLimit></span>

r-show-变量

响应变量值的变化,若变量值为 falsy,则将元素的 display 设为 none 。

html 复制代码
<span r-show-isShowLimit></span>

r-model-变量

双向绑定,既响应变量值的变化,也监听 input 事件改变值。

html 复制代码
<input type="text" r-model-todoName>

r-for-变量

列表渲染,元素的 innerHTML 作为模板项循环渲染,若 innerHTML 为空则默认模板为 <div>r-prop</div>

html 复制代码
<div r-for-simpleList></div>

r-prop-属性

仅用于 r-for 内的模板,在列表渲染时被直接替换为当前项的对应属性值,r-prop 则替换为当前项。

html 复制代码
<select r-model-todoType r-for-typeList>
  <option value="r-prop-value">r-prop-label</option>
</select>

<div r-for-simpleList><span>r-prop</span></div>

r-event="事件:方法"

为元素添加事件监听,方法为 methods 声明的方法函数名,方法的参数为原生事件的 event 。

r-for 里的 r-event 方法有两个参数,第一个是原生事件的 event,第二个是 r-for 的索引。

html 复制代码
<button r-event="click:addTodo">添加待办</button>

<div r-for-todoListFormat>
  <div>
    <span>r-prop-name</span>
    <span>r-prop-type</span>
    <span>r-prop-time</span>
    <button r-event="click:delTodo">删除</button>
  </div>
</div>
js 复制代码
methods: {
  addTodo(event) {
    //
  },
  delTodo(event, index) {
    //
  }
}

Demo

Rea.js Example - Todo List

源码

Rea.js 的所有源码,不到 100 行。

js 复制代码
function Rea(options) {
  const computedMap = {}
  const modelListener = []
  const templateMap = new Map()

  const proxy = new Proxy(options.data, {
    set(target, propKey, value, receiver) {
      if (Array.isArray(value)) {
        value = new Proxy(value, {
          set(_target, _propKey, _value, _receiver) {
            setTimeout(() => {
              proxy[propKey] = _target.slice()
            })
            return Reflect.set(_target, _propKey, _value, _receiver)
          }
        })
      }
      setTimeout(() => {
        const nodes = document.querySelectorAll(`[r-${propKey}]`)
        for (let node of nodes || []) {
          node.textContent = value
        }

        const showNodes = document.querySelectorAll(`[r-show-${propKey}]`)
        for (let node of showNodes || []) {
          if (value) node.style.display = ''
          else node.style.display = 'none'
        }

        const modelNodes = document.querySelectorAll(`[r-model-${propKey}]`)
        for (let node of modelNodes || []) {
          node.value = value
          if (!modelListener.includes(node)) {
            modelListener.push(node)
            node.addEventListener('input', () => {
              proxy[propKey] = node.value
            })
          }
        }

        const forNodes = document.querySelectorAll(`[r-for-${propKey}]`)
        for (let node of forNodes || []) {
          if (!templateMap.get(node)) {
            templateMap.set(node, node.innerHTML || '<div>r-prop</div>')
          }
          node.innerHTML = value
            .map((m, i) =>
              templateMap
                .get(node)
                .replace(/r-prop-(\w+)/g, (_, key) => m[key])
                .replace(/r-prop/g, m)
                .replace(/r-event="/g, 'r-event="' + i + ':')
            )
            .join('')

          const eventNodes = node.querySelectorAll('[r-event]')
          for (let eNode of eventNodes || []) {
            const [index, name, method] = eNode.getAttribute('r-event').split(':')
            eNode.addEventListener(name, event => {
              proxy[method](event, +index)
            })
          }
        }

        for (let item of computedMap[propKey] || []) {
          proxy[item] = options.computed[item].call(proxy)
        }
      })

      return Reflect.set(target, propKey, value, receiver)
    }
  })

  for (let key in options.data) {
    proxy[key] = options.data[key]
  }

  for (let key in options.computed) {
    const initProxy = new Proxy(options.data, {
      get(target, propKey, receiver) {
        if (!computedMap[propKey]) computedMap[propKey] = new Set()
        computedMap[propKey].add(key)
        return Reflect.get(target, propKey, receiver)
      }
    })
    proxy[key] = options.computed[key].call(initProxy)
  }

  for (let key in options.methods) {
    proxy[key] = options.methods[key].bind(proxy)
  }

  const eventNodes = document.querySelectorAll('[r-event]')
  for (let node of eventNodes || []) {
    const [name, method] = node.getAttribute('r-event').split(':')
    node.addEventListener(name, proxy[method])
  }
}
相关推荐
卓大胖_1 小时前
Next.js 新手容易犯的错误 _ 性能优化与安全实践(6)
前端·javascript·安全
CodeClimb1 小时前
【华为OD-E卷 - 猜字谜100分(python、java、c++、js、c)】
java·javascript·c++·python·华为od
程序员_三木2 小时前
在 Vue3 项目中安装和配置 Three.js
前端·javascript·vue.js·webgl·three.js
徐_三岁2 小时前
Vue3 Suspense:处理异步渲染过程
前端·javascript·vue.js
萧寂1732 小时前
Pinia最简单使用(vite+vue3)
前端·javascript·vue.js
涔溪2 小时前
Vue axios 异步请求,请求响应拦截器
前端·javascript·vue.js
darling3312 小时前
vue+elementUI 表单项赋值后无法修改的问题
前端·javascript·vue.js·elementui·ecmascript
呆呆小雅3 小时前
四、Vue 条件语句
前端·javascript·vue.js
LUwantAC3 小时前
一篇文章学会HTML
前端·javascript·html
发呆的薇薇°3 小时前
React里使用lodash工具库
javascript·react.js