百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

使用现代原生 JavaScript 实现反应性模式

toyiye 2024-08-13 11:40 12 浏览 0 评论

“反应性”是指系统对数据变化的反应。反应性有很多种,但在本文中,反应性是指 当数据发生变化时,你采取的行动。

响应式模式是 Web 开发的核?心

由于浏览器是一个完全异步的环境,因此我们在网站和 Web 应用中使用 JavaScript 处理大量事务。我们必须响应用户输入、与服务器通信、记录、执行等。所有这些任务都涉及 UI 更新、Ajax 请求、浏览器 URL 和导航更改,因此级联数据更改成为 Web 开发的核??心方面。

作为一个行业,我们将反应性与框架联系在一起,但您可以通过在纯 JavaScript 中实现反应性学到很多东西。我们可以混合搭配这些模式,将行为与数据变化联系起来。

无论您使用什么工具或框架,学习纯 JavaScript 的核心模式都会减少代码量并提高 Web 应用程序的性能。

我喜欢学习模式,因为它们适用于任何语言和系统。模式可以组合起来解决您应用的确切需求,通常可以提高代码的性能和可维护性。

希望您能学到新的模式来添加到您的工具箱中,无论您使用什么框架和库!

PubSub 模式(发布订阅者)

PubSub 是响应式的最基础模式之一。触发事件 publish() 允许任何人监听该事件 subscribe() ,并在与触发该事件无关的情况下做任何他们想做的事情。

const pubSub = {
  events: {},
  subscribe(event, callback) {
    if (!this.events[event]) this.events[event] = [];
    this.events[event].push(callback);
  },
  publish(event, data) {
    if (this.events[event]) this.events[event].forEach(callback => callback(data));
  }
};

pubSub.subscribe('update', data => console.log(data));
pubSub.publish('update', 'Some update'); // Some update
 JavaScript 

请注意,发布者 不知道 正在收听什么,因此无法通过这种简单的实现来取消订阅或进行清理。

自定义事件:PubSub 的本机浏览器 API

浏览器具有用于触发和订阅自定义事件的 JavaScript API。它允许您使用 随自定义事件一起发送数据 dispatchEvent。

const pizzaEvent = new CustomEvent("pizzaDelivery", {
  detail: {
    name: "supreme",
  },
});

window.addEventListener("pizzaDelivery", (e) => console.log(e.detail.name));
window.dispatchEvent(pizzaEvent);
 JavaScript 

您可以将这些自定义事件的范围限定为任何 DOM 节点。在代码示例中,我们使用全局 window 对象(也称为全局事件总线),因此我们应用中的任何内容都可以监听事件数据并执行某些操作。

<div id="pizza-store"></div>
  
const pizzaEvent = new CustomEvent("pizzaDelivery", {
  detail: {
    name: "supreme",
  },
});

const pizzaStore = document.querySelector('#pizza-store');
pizzaStore.addEventListener("pizzaDelivery", (e) => console.log(e.detail.name));
pizzaStore.dispatchEvent(pizzaEvent);

类实例自定义事件:子类化 EventTarget

我们可以将 EventTarget 子类化,以便在类实例上发送事件,以供我们的应用程序绑定:

class PizzaStore extends EventTarget {
  constructor() {
    super();
  }
  addPizza(flavor) {
    // fire event directly on the class
    this.dispatchEvent(new CustomEvent("pizzaAdded", {
      detail: {
        pizza: flavor,
      },
    }));
  }
}

const Pizzas = new PizzaStore();
Pizzas.addEventListener("pizzaAdded", (e) => console.log('Added Pizza:', e.detail.pizza));
Pizzas.addPizza("supreme");
 JavaScript 

很酷的一点是,您的事件不会在窗口上全局触发。您可以直接在类上触发事件;应用中的任何内容都可以将事件侦听器直接连接到该类。

观察者模式

观察者模式与 PubSub 模式的基本前提相同。它允许您将行为“订阅”到主题。当主题触发该 notify 方法时,它会通知所有订阅的内容。

class Subject {
  constructor() {
    this.observers = [];
  }
  addObserver(observer) {
    this.observers.push(observer);
  }
  removeObserver(observer) {
    const index = this.observers.indexOf(observer);
    if (index > -1) {
      this.observers.splice(index, 1);
    }
  }
  notify(data) {
    this.observers.forEach(observer => observer.update(data));
  }
}

class Observer {
  update(data) {
    console.log(data);
  }
}

const subject = new Subject();
const observer = new Observer();

subject.addObserver(observer);
subject.notify('Everyone gets pizzas!');
 JavaScript 

它与 PubSub 的主要区别在于,Subject 知道其观察者并可以将其移除。它们并不 像 PubSub 中那样完全解耦。

具有代理的反应性对象属性

JavaScript 中的代理可以作为设置或获取对象属性后执行反应性的基础。

const handler = {
  get: function(target, property) {
    console.log(`Getting property ${property}`);
    return target[property];
  },
  set: function(target, property, value) {
    console.log(`Setting property ${property} to ${value}`);
    target[property] = value;
    return true; // indicates that the setting has been done successfully
  }
};

const pizza = { name: 'Margherita', toppings: ['tomato sauce', 'mozzarella'] };
const proxiedPizza = new Proxy(pizza, handler);

console.log(proxiedPizza.name); // Outputs "Getting property name" and "Margherita"
proxiedPizza.name = 'Pepperoni'; // Outputs "Setting property name to Pepperoni"
 JavaScript 

当您访问或修改 上的属性时 proxiedPizza,它会将一条消息记录到控制台。但您可以想象将任何功能连接到对象的属性访问。

反应性单个属性:Object.defineProperty

您可以使用 对特定属性执行相同的操作 Object.defineProperty。您可以为属性定义 getter 和 setter,并在访问或修改属性时运行代码。

const pizza = {
  _name: 'Margherita', // Internal property
};

Object.defineProperty(pizza, 'name', {
  get: function() {
    console.log(`Getting property name`);
    return this._name;
  },
  set: function(value) {
    console.log(`Setting property name to ${value}`);
    this._name = value;
  }
});

// Example usage:
console.log(pizza.name); // Outputs "Getting property name" and "Margherita"
pizza.name = 'Pepperoni'; // Outputs "Setting property name to Pepperoni"
 JavaScript 

这里,我们使用 Object.defineProperty getter 和 setter 来为 pizza 对象的 name 属性定义一个 getter 和 setter。实际值存储在一个私有 _name 属性中,getter 和 setter 可以在将消息记录到控制台时访问该值。

Object.defineProperty 比使用 更冗长 Proxy,特别是当您想将相同的行为应用于许多属性时。但它是一种为各个属性定义自定义行为的强大而灵活的方法。

使用 Promises 实现异步响应数据

让我们异步使用观察者!这样我们就可以更新数据并让多个观察者异步运行。

class AsyncData {
  constructor(initialData) {
    this.data = initialData;
    this.subscribers = [];
  }

  // Subscribe to changes in the data
  subscribe(callback) {
    if (typeof callback !== 'function') {
      throw new Error('Callback must be a function');
    }
    this.subscribers.push(callback);
  }

  // Update the data and wait for all updates to complete
  async set(key, value) {
    this.data[key] = value;

    // Call the subscribed function and wait for it to resolve
    const updates = this.subscribers.map(async (callback) => {
      await callback(key, value);
    });

    await Promise.allSettled(updates);
  }
}

这是一个包装数据对象并在数据改变时触发更新的类。

等待我们的异步观察者

假设我们要等到所有异步反应数据的订阅都处理完毕:

const data = new AsyncData({ pizza: 'Pepperoni' });

data.subscribe(async (key, value) => {
  await new Promise(resolve => setTimeout(resolve, 500));
  console.log(`Updated UI for ${key}: ${value}`);
});

data.subscribe(async (key, value) => {
  await new Promise(resolve => setTimeout(resolve, 1000));
  console.log(`Logged change for ${key}: ${value}`);
});

// function to update data and wait for all updates to complete
async function updateData() {
  await data.set('pizza', 'Supreme'); // This will call the subscribed functions and wait for their promises to resolve
  console.log('All updates complete.');
}

updateData();
 JavaScript 

我们的 updateData 函数现在是异步的,因此我们可以等待所有订阅的函数解析后再继续执行程序。这种模式可以让异步响应变得更简单一些。

反应系统

许多更复杂的响应式系统都是流行库和框架的基础:React 中的钩子、Solid 中的信号、Rx.js 中的可观察对象等等。它们通常具有相同的基本原理,即当数据发生变化时,重新渲染组件或相关的 DOM 片段。

可观察对象(Rx.js 模式)

尽管可观察对象和观察者模式几乎是同一个词,但事实并不相同,哈哈。

Observables 允许您定义一种随时间产生一系列值的方法。这是一个简单的 Observable 原语,它提供了一种向订阅者发出一系列值的方法,允许订阅者在产生这些值时做出反应。

class Observable {
  constructor(producer) {
    this.producer = producer;
  }

  // Method to allow a subscriber to subscribe to the observable
  subscribe(observer) {
    // Ensure the observer has the necessary functions
    if (typeof observer !== 'object' || observer === null) {
      throw new Error('Observer must be an object with next, error, and complete methods');
    }

    if (typeof observer.next !== 'function') {
      throw new Error('Observer must have a next method');
    }

    if (typeof observer.error !== 'function') {
      throw new Error('Observer must have an error method');
    }

    if (typeof observer.complete !== 'function') {
      throw new Error('Observer must have a complete method');
    }

    const unsubscribe = this.producer(observer);

    // Return an object with an unsubscribe method
    return {
      unsubscribe: () => {
        if (unsubscribe && typeof unsubscribe === 'function') {
          unsubscribe();
        }
      },
    };
  }
}
 JavaScript 

使用方法如下:

// Create a new observable that emits three values and then completes
const observable = new Observable(observer => {
  observer.next(1);
  observer.next(2);
  observer.next(3);
  observer.complete();

  // Optional: Return a function to handle any cleanup if the observer unsubscribes
  return () => {
    console.log('Observer unsubscribed');
  };
});

// Define an observer with next, error, and complete methods
const observer = {
  next: value => console.log('Received value:', value),
  error: err => console.log('Error:', err),
  complete: () => console.log('Completed'),
};

// Subscribe to the observable
const subscription = observable.subscribe(observer);

// Optionally, you can later unsubscribe to stop receiving values
subscription.unsubscribe();
 JavaScript 

Observable 的关键组件是 next() 向观察者发送数据的方法。Observable complete() 流关闭时的方法。以及 error() 发生错误时的方法。此外,还必须有一种方法来 subscribe() 监听更改并 unsubscribe() 停止从流中接收数据。

使用此模式的最流行的库是 Rx.js 和 MobX。

“信号” (SolidJS 模式)

const context = [];

export function createSignal(value) {
    const subscriptions = new Set();

    const read = () => {
        const observer = context[context.length - 1]
        if (observer) subscriptions.add(observer);
        return value;
    }
    const write = (newValue) => {
        value = newValue;
        for (const observer of subscriptions) {
            observer.execute()
        }
    }

    return [read, write];
}

export function createEffect(fn) {
    const effect = {
        execute() {
            context.push(effect);
            fn();
            context.pop();
        }
    }

    effect.execute();
}

使用反应系统:

import { createSignal, createEffect } from "./reactive";

const [count, setCount] = createSignal(0);

createEffect(() => {
  console.log(count());
}); // 0

setCount(10); // 10

UI 的反应式渲染

以下是一些从 DOM 和 CSS 写入和读取的模式。

将数据渲染为 HTML 字符串文字

这是一个根据数据渲染一些披萨 UI 的简单示例。

function PizzaRecipe(pizza) {
  return `<div class="pizza-recipe">
    <h1>${pizza.name}</h1>
    <h3>Toppings: ${pizza.toppings.join(', ')}</h3>
    <p>${pizza.description}</p>
  </div>`;
}

function PizzaRecipeList(pizzas) {
  return `<div class="pizza-recipe-list">
    ${pizzas.map(PizzaRecipe).join('')}
  </div>`;
}

var allPizzas = [
  {
    name: 'Margherita',
    toppings: ['tomato sauce', 'mozzarella'],
    description: 'A classic pizza with fresh ingredients.'
  },
  {
    name: 'Pepperoni',
    toppings: ['tomato sauce', 'mozzarella', 'pepperoni'],
    description: 'A favorite among many, topped with delicious pepperoni.'
  },
  {
    name: 'Veggie Supreme',
    toppings: ['tomato sauce', 'mozzarella', 'bell peppers', 'onions', 'mushrooms'],
    description: 'A delightful vegetable-packed pizza.'
  }
];

// Render the list of pizzas
function renderPizzas() {
  document.querySelector('body').innerHTML = PizzaRecipeList(allPizzas);
}

renderPizzas(); // Initial render

// Example of changing data and re-rendering
function addPizza() {
  allPizzas.push({
    name: 'Hawaiian',
    toppings: ['tomato sauce', 'mozzarella', 'ham', 'pineapple'],
    description: 'A tropical twist with ham and pineapple.'
  });

  renderPizzas(); // Re-render the updated list
}

// Call this function to add a new pizza and re-render the list
addPizza();
 JavaScript 

addPizza 演示如何通过向列表添加新的披萨食谱来更改数据,然后重新呈现列表以反映更改。

这种方法的主要缺点是每次渲染时都会毁掉整个 DOM。您可以使用 lit-html之类的库 (lit-html 使用指南)更智能地仅更新发生变化的 DOM 位。我们在 Frontend Masters 上使用几个高度动态的组件(例如我们的数据网格组件)来实现这一点。

反应式 DOM 属性:MutationObserver

使 DOM 具有响应性的一种方法是添加和删除属性。我们可以使用 API 监听属性的变化 MutationObserver 。

const mutationCallback = (mutationsList) => {
  for (const mutation of mutationsList) {
    if (
      mutation.type !== "attributes" ||
      mutation.attributeName !== "pizza-type"
    ) return;

    console.log('old:', mutation.oldValue)
    console.log('new:', mutation.target.getAttribute("pizza-type"))
  }
}
const observer = new MutationObserver(mutationCallback);
observer.observe(document.getElementById('pizza-store'), { attributes: true });
 JavaScript 

现在我们可以从程序中的任何位置更新披萨类型属性,并且元素本身可以具有附加更新该属性的行为!

Web 组件中的反应性属性

使用 Web 组件,有一种本机的方式来监听和响应属性更新。

class PizzaStoreComponent extends HTMLElement {
  static get observedAttributes() {
    return ['pizza-type'];
  }

  constructor() {
    super();
    const shadowRoot = this.attachShadow({ mode: 'open' });
    shadowRoot.innerHTML = `<p>${this.getAttribute('pizza-type') || 'Default Content'}</p>`;
  }

  attributeChangedCallback(name, oldValue, newValue) {
    if (name === 'my-attribute') {
      this.shadowRoot.querySelector('div').textContent = newValue;
      console.log(`Attribute ${name} changed from ${oldValue} to ${newValue}`);
    }
  }
}

customElements.define('pizza-store', PizzaStoreComponent);
 JavaScript 
<pizza-store pizza-type="Supreme"></pizza-store>
  
document.querySelector('pizza-store').setAttribute('pizza-type', 'BBQ Chicken!');
 JavaScript 

这个有点简单,但是我们必须使用 Web 组件才能使用这个 API。

响应式滚动:IntersectionObserver

我们可以将响应性连接到滚动到视图中的 DOM 元素。我已将其用于营销页面上的流畅动画。

var pizzaStoreElement = document.getElementById('pizza-store');

var observer = new IntersectionObserver(function(entries, observer) {
  entries.forEach(function(entry) {
    if (entry.isIntersecting) {
      entry.target.classList.add('animate-in');
    } else {
      entry.target.classList.remove('animate-in');
    }
  });
});

observer.observe(pizzaStoreElement);

动画和游戏循环:requestAnimationFrame

在进行游戏开发、Canvas、WebGL 或那些疯狂的营销网站时,动画通常需要写入缓冲区,然后在渲染线程可用时在给定循环中写入结果。我们使用 来实现这一点 requestAnimationFrame。

function drawStuff() {
  // This is where you'd do game or animation rendering logic
}

// function to handle the animation
function animate() {
  drawStuff();
  requestAnimationFrame(animate); // Continually calls animate when the next render frame is available
}

// Start the animation
animate();
 JavaScript 

这是游戏以及任何涉及实时渲染的内容在帧可用时渲染场景的方法。

响应式动画:Web 动画 API

您还可以使用 Web Animations API 创建响应式动画。在这里,我们将使用动画 API 为元素的比例、位置和颜色设置动画。

const el = document.getElementById('animatedElement');

// Define the animation properties
const animation = el.animate([
  // Keyframes
  { transform: 'scale(1)', backgroundColor: 'blue', left: '50px', top: '50px' },
  { transform: 'scale(1.5)', backgroundColor: 'red', left: '200px', top: '200px' }
], {
  // Timing options
  duration: 1000,
  fill: 'forwards'
});

// Set the animation's playback rate to 0 to pause it
animation.playbackRate = 0;

// Add a click event listener to the element
el.addEventListener('click', () => {
  // If the animation is paused, play it
  if (animation.playbackRate === 0) {
    animation.playbackRate = 1;
  } else {
    // If the animation is playing, reverse it
    animation.reverse();
  }
});
 JavaScript 

其响应性在??于,动画可以在发生交互时(在本例中为反转方向)相对于其所在位置播放。标准 CSS 动画和过渡不相对于其当前位置。

响应式 CSS:自定义属性和 calc

最后,我们可以通过组合自定义属性和来编写反应式 CSS calc。

barElement.style.setProperty('--percentage', newPercentage);
 JavaScript 

在 JavaScript 中,您可以设置自定义属性值。

.bar {
  width: calc(100% / 4 - 10px);
  height: calc(var(--percentage) * 1%);
  background-color: blue;
  margin-right: 10px;
  position: relative;
}
 CSS 

在 CSS 中,我们现在可以根据该百分比进行计算。我们可以直接在 CSS 中添加计算,让 CSS 完成其样式设置工作,而无需将所有渲染逻辑保留在 JavaScript 中,这真是太酷了。

仅供参考:如果您想创建相对于当前值的更改,您也可以读取这些属性。

getComputedStyle(barElement).getPropertyValue('--percentage');

相关推荐

# Python 3 # Python 3字典Dictionary(1)

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值(key=>value)对用冒号(:)分割,每个对之间用逗号(,)分割,整个字典包括在花括号({})中,格式如...

Python第八课:数据类型中的字典及其函数与方法

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值...

Python中字典详解(python 中字典)

字典是Python中使用键进行索引的重要数据结构。它们是无序的项序列(键值对),这意味着顺序不被保留。键是不可变的。与列表一样,字典的值可以保存异构数据,即整数、浮点、字符串、NaN、布尔值、列表、数...

Python3.9又更新了:dict内置新功能,正式版十月见面

机器之心报道参与:一鸣、JaminPython3.8的热乎劲还没过去,Python就又双叒叕要更新了。近日,3.9版本的第四个alpha版已经开源。从文档中,我们可以看到官方透露的对dic...

Python3 基本数据类型详解(python三种基本数据类型)

文章来源:加米谷大数据Python中的变量不需要声明。每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建。在Python中,变量就是变量,它没有类型,我们所说的"类型"是变...

一文掌握Python的字典(python字典用法大全)

字典是Python中最强大、最灵活的内置数据结构之一。它们允许存储键值对,从而实现高效的数据检索、操作和组织。本文深入探讨了字典,涵盖了它们的创建、操作和高级用法,以帮助中级Python开发...

超级完整|Python字典详解(python字典的方法或操作)

一、字典概述01字典的格式Python字典是一种可变容器模型,且可存储任意类型对象,如字符串、数字、元组等其他容器模型。字典的每个键值key=>value对用冒号:分割,每个对之间用逗号,...

Python3.9版本新特性:字典合并操作的详细解读

处于测试阶段的Python3.9版本中有一个新特性:我们在使用Python字典时,将能够编写出更可读、更紧凑的代码啦!Python版本你现在使用哪种版本的Python?3.7分?3.5分?还是2.7...

python 自学,字典3(一些例子)(python字典有哪些基本操作)

例子11;如何批量复制字典里的内容2;如何批量修改字典的内容3;如何批量修改字典里某些指定的内容...

Python3.9中的字典合并和更新,几乎影响了所有Python程序员

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

Python3大字典:《Python3自学速查手册.pdf》限时下载中

最近有人会想了,2022了,想学Python晚不晚,学习python有前途吗?IT行业行业薪资高,发展前景好,是很多求职群里严重的香饽饽,而要进入这个高薪行业,也不是那么轻而易举的,拿信工专业的大学生...

python学习——字典(python字典基本操作)

字典Python的字典数据类型是基于hash散列算法实现的,采用键值对(key:value)的形式,根据key的值计算value的地址,具有非常快的查取和插入速度。但它是无序的,包含的元素个数不限,值...

324页清华教授撰写【Python 3 菜鸟查询手册】火了,小白入门字典

如何入门学习python...

Python3.9中的字典合并和更新,了解一下

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

python3基础之字典(python中字典的基本操作)

字典和列表一样,也是python内置的一种数据结构。字典的结构如下图:列表用中括号[]把元素包起来,而字典是用大括号{}把元素包起来,只不过字典的每一个元素都包含键和值两部分。键和值是一一对应的...

取消回复欢迎 发表评论:

请填写验证码