JavaScript类,如何在实践中应用 "关注点分离 "和 "不要重复自己"(DRY)。

问题描述 投票:0回答:2

我正在学习JavaScript类如何工作,我只是想寻求一些建议,如何实现一些很简单的东西,我希望关于动画的一些元素。

我创建了一个名为 myAnimation构造函数的参数只有一个,那就是一个元素,它所做的就是将一个标题淡出和淡入,一切都很简单。它所做的只是将一个标题淡出和淡入,一切都很简单。当页面上只有一个标题元素时,它可以正常工作,我只是不确定如何让它在多个标题下工作。

请原谅我的天真;对我来说,这一切都很新奇,这只是我自己做的一个基本例子,试图帮助自己理解它是如何工作的。

class myAnimation {
  constructor(element) {
    this.element = document.querySelector(element);
  }
  fadeOut(time) {
    if (this.element.classList.contains('fadeout-active')) {
      this.element.style.opacity = 1;
      this.element.classList.remove('fadeout-active');
      button.textContent = 'Hide Heading';
    } else {
      this.element.style.opacity = 0;
      this.element.style.transition = `all ${time}s ease`;
      this.element.classList.add('fadeout-active');
      button.textContent = 'Show Heading';
    }
  }
}

const heading = new myAnimation('.heading');
const button = document.querySelector('.button');
button.addEventListener('click', () => {
  heading.fadeOut(1);

});
<div class="intro">
  <h1 class="heading">Intro Heading</h1>
  <p>This is the intro section</p>
  <button class="button">Hide Heading</button>
</div>

<div class="main">
  <h1 class="heading">Main Heading</h1>
  <p>This is the main section</p>
  <button class="button">Hide Heading</button>
</div>
javascript class widget dry separation-of-concerns
2个回答
2
投票

在我的评论之后,我想让这个脚本以我认为可能是上位者所希望的方式运行。

尽管它演示了为了正常运行所需要做的事情,但整个基础设计证明并不适合OP真正可能需要实现的目标。

这个类叫做 Animation 但从一开始,它就将元素动画和变化状态的单一元素交织在一起,以某种方式在全球范围内进行 button.

即使现在正在运行,这个设计也不能证明是真正合适的,因为现在我们把将要被动画化的元素和与之交互的按钮一起传递到构造函数中。

功能的分组是正确的,只是地方和命名并不真正合适。

上位者可以考虑下一步对提供的代码进行迭代....

class Animation {
  constructor(elementNode, buttonNode) {

    this.element = elementNode;
    this.button = buttonNode;

    // only in case both elements were passed ...
    if (elementNode && buttonNode) {
    
      // couple them by event listening/handling.
      buttonNode.addEventListener('click', () => {

        // - accessing the `Animation` instance's `this` context
        //   gets assured by making use of an arrow function.
        this.fadeOut(1);
      });
    }
  }
  fadeOut(time) {
    if (this.element.classList.contains('fadeout-active')) {

      this.element.style.opacity = 1;
      this.element.classList.remove('fadeout-active');

      this.button.textContent = 'Hide Heading';
    } else {
      this.element.style.opacity = 0;
      this.element.style.transition = `all ${time}s ease`;
      this.element.classList.add('fadeout-active');

      this.button.textContent = 'Show Heading';
    }
  }
}

function initializeAnimations() {
  // get list of all elements that have a `heading` class name.
  const headingList = document.querySelectorAll('.heading');

  // for each heading element do ...
  headingList.forEach(function (headingNode) {
    // ... access its parent element and query again for a single button.
    const buttonNode = headingNode.parentElement.querySelector('.button');
    
    // if the related button element exists ...
    if (buttonNode) {

      // ... create a new `Animation` instance.
      new Animation(headingNode, buttonNode);
    }
  });
}

initializeAnimations();
.as-console-wrapper { max-height: 100%!important; top: 0; }
<div class="intro">
  <h1 class="heading">Intro Heading</h1>
  <p>This is the intro section</p>
  <button class="button">Hide Heading</button>
</div>

<div class="main">
  <h1 class="heading">Main Heading</h1>
  <p>This is the main section</p>
  <button class="button">Hide Heading</button>
</div>

.新的一天,下一个可能的迭代步骤......。

第2次迭代分离关注。

它通过重命名类并只实现类的特定行为来实现。因此,一个 FadeToggle 类只提供了 拨动特定 功能。

然后,代码会被分割成两个处理初始化的函数。为了更好的重用,初始化代码和html结构需要重构成更通用的东西。这个 data 容器的属性,每个容器都有一个用于淡化目标元素的触发元素,它将被用作配置存储,为初始化过程提供所有必要的信息。甚至可以提供单独的过渡持续时间值)。

最后,还有一个处理函数,它的实现方式是可以由 bind 以生成一个闭合,为每个项目提供所有必要的数据。触发-目标情侣.

class FadeToggle {
  // a clean fade-toggle implementation.
  constructor(elementNode, duration) {

    duration = parseFloat(duration, 10);
    duration = Number.isFinite(duration) ? duration : 1;

    elementNode.style.opacity = 1;
    elementNode.style.transition = `all ${ duration }s ease`;

    this.element = elementNode;
  }

  isFadeoutActive() {
    return this.element.classList.contains('fadeout-active');
  }

  toggleFade(duration) {
    duration = parseFloat(duration, 10);
    if (Number.isFinite(duration)) {

      this.element.style.transitionDuration = `${ duration }s`;
    }
    if (this.isFadeoutActive()) {

      this.element.style.opacity = 1;
      this.element.classList.remove('fadeout-active');
    } else {
      this.element.style.opacity = 0;
      this.element.classList.add('fadeout-active');
    }
  }
}

function handleFadeToggleWithBoundContext(/* evt */) {
  const { trigger, target } = this;

  if (target.isFadeoutActive()) {
    trigger.textContent = 'Hide Heading';
  } else {
    trigger.textContent = 'Show Heading';
  }
  target.toggleFade();
}

function initializeFadeToggle(elmNode) {
  // parse an element node's fade-toggle configuration.
  const config = JSON.parse(elmNode.dataset.fadeToggleConfig || null);

  const selectors = (config && config.selectors);
  if (selectors) {
    try {
      // query both the triggering and the target element
      const trigger = elmNode.querySelector(selectors.trigger || null);
      let target = elmNode.querySelector(selectors.target || null);

      if (trigger && target) {

        // create a `FadeToggle` target type.
        target = new FadeToggle(target, config.duration);

        // couple trigger and target by event listening/handling ...
        trigger.addEventListener(
          'click',
          handleFadeToggleWithBoundContext.bind({
            // ... and binding both as context properties to the handler.
            trigger,
            target
          })
        );
      }
    } catch (exception) {
      console.warn(exception.message, exception);
    }
  }
}

function initializeEveryFadeToggle() {
  // get list of all elements that contain a fade-toggle configuration
  const configContainerList = document.querySelectorAll('[data-fade-toggle-config]');

  // do initialization for each container separately.
  configContainerList.forEach(initializeFadeToggle);
}

initializeEveryFadeToggle();
.as-console-wrapper { max-height: 100%!important; top: 0; }
<div class="intro" data-fade-toggle-config='{"selectors":{"trigger":".button","target":".heading"},"duration":3}'>
  <h1 class="heading">Intro Heading</h1>
  <p>This is the intro section</p>
  <button class="button">Hide Heading</button>
</div>

<div class="main" data-fade-toggle-config='{"selectors":{"trigger":".button","target":".heading"}}'>
  <h1 class="heading">Main Heading</h1>
  <p>This is the main section</p>
  <button class="button">Hide Heading</button>
</div>

.下午,改进状态变化的处理方法。

还是有硬线数据,直接写进代码。为了摆脱每次切换发生时都会(重新)呈现的字符串值,我们可以给 data-基于配置的方法,再给一次机会。

这一次,每个触发元素都可能有一个提供状态值的配置。因此,初始化过程需要负责检索这些数据,并根据淡入淡出目标的初始状态进行渲染。

这个目标直接带来了触发器元素渲染函数的必要性,因为人们不仅需要改变触发器的初始状态,还需要在每次淡入淡出时改变其状态。

而这又将改变处理函数的方式,使其具有绑定状态值的功能,以便将这些数据委托给渲染过程......。

class FadeToggle {
  // a clean fade-toggle implementation.
  constructor(elementNode, duration) {

    duration = parseFloat(duration, 10);
    duration = Number.isFinite(duration) ? duration : 1;

    elementNode.style.opacity = 1;
    elementNode.style.transition = `all ${ duration }s ease`;

    this.element = elementNode;
  }

  isFadeoutActive() {
    return this.element.classList.contains('fadeout-active');
  }

  toggleFade(duration) {
    duration = parseFloat(duration, 10);
    if (Number.isFinite(duration)) {

      this.element.style.transitionDuration = `${ duration }s`;
    }
    if (this.isFadeoutActive()) {

      this.element.style.opacity = 1;
      this.element.classList.remove('fadeout-active');
    } else {
      this.element.style.opacity = 0;
      this.element.classList.add('fadeout-active');
    }
  }
}

function renderTargetStateDependedTriggerText(target, trigger, fadeinText, fadeoutText) {
  if ((fadeinText !== null) && (fadeoutText !== null)) {
    if (target.isFadeoutActive()) {

      trigger.textContent = fadeinText;
    } else {
      trigger.textContent = fadeoutText;
    }
  }
}

function handleFadeToggleWithBoundContext(/* evt */) {
  // retrieve context data.
  const { target, trigger, fadeinText, fadeoutText } = this;

  target.toggleFade();

  renderTargetStateDependedTriggerText(
    target,
    trigger,
    fadeinText,
    fadeoutText
  );
}

function initializeFadeToggle(elmNode) {
  // parse an element node's fade-toggle configuration.
  let config = JSON.parse(elmNode.dataset.fadeToggleConfig || null);

  const selectors = (config && config.selectors);
  if (selectors) {
    try {
      // query both the triggering and the target element
      const trigger = elmNode.querySelector(selectors.trigger || null);
      let target = elmNode.querySelector(selectors.target || null);

      if (trigger && target) {

        // create a `FadeToggle` target type.
        target = new FadeToggle(target, config.duration);

        // parse a trigger node's fade-toggle configuration and state.
        const triggerStates = ((
          JSON.parse(trigger.dataset.fadeToggleTriggerConfig || null)
          || {}
        ).states || {});

        // get a trigger node's state change values.
        const fadeinStateValues = (triggerStates.fadein || {});
        const fadeoutStateValues = (triggerStates.fadeout || {});

        // get a trigger node's state change text contents.
        const fadeinText = fadeinStateValues.textContent || null;
        const fadeoutText = fadeoutStateValues.textContent || null;

        // rerender trigger node's initial text value.
        renderTargetStateDependedTriggerText(
          target,
          trigger,
          fadeinText,
          fadeoutText
        );

        // couple trigger and target by event listening/handling ...
        trigger.addEventListener(
          'click',
          handleFadeToggleWithBoundContext.bind({
            // ... and by binding both and some text values
            // that are sensitive to state changes
            // as context properties to the handler.
            target,
            trigger,
            fadeinText,
            fadeoutText
          })
        );
      }
    } catch (exception) {
      console.warn(exception.message, exception);
    }
  }
}

function initializeEveryFadeToggle() {
  // get list of all elements that contain a fade-toggle configuration
  const configContainerList = document.querySelectorAll('[data-fade-toggle-config]');

  // do initialization for each container separately.
  configContainerList.forEach(initializeFadeToggle);
}

initializeEveryFadeToggle();
.as-console-wrapper { max-height: 100%!important; top: 0; }
<div class="intro" data-fade-toggle-config='{"selectors":{"trigger":".button","target":".heading"},"duration":3}'>
  <h1 class="heading">Intro Heading</h1>
  <p>This is the intro section</p>
  <button class="button" data-fade-toggle-trigger-config='{"states":{"fadeout":{"textContent":"Hide Heading"},"fadein":{"textContent":"Show Heading"}}}'>Toggle Heading</button>
</div>

<div class="main" data-fade-toggle-config='{"selectors":{"trigger":".button","target":".heading"}}'>
  <h1 class="heading">Main Heading</h1>
  <p>This is the main section</p>
  <button class="button">Toggle Heading</button>
</div>

0
投票

出现这种情况是因为 document.querySelector(".button") 只返回第一个带类的元素 .button (参考).

你可能想试试 document.querySelectorAll(".button") (参考) 来添加你的事件监听器。

(尽管这只会切换你的第一个标题--出于同样的原因。)

© www.soinside.com 2019 - 2024. All rights reserved.