Когда АОП и реакция работают хорошо, у меня меньше кода

React.js

предисловие

АОП (аспектно-ориентированное программирование) извлекает то, что делается в некоторые ключевые моменты/критические моменты в бизнесе (то есть аспекты), что является ключевым шагом в процессе выполнения кода. Проще говоря, АОП фокусируется на том, какое поведение/определение в какой момент времени.

Быстро понять разницу между АОП и ООП

ООП (объектно-ориентированное программирование) должно быть знакомо фронтендеру, давайте возьмем пример для сравнения АОП и ООП.

OOP

Предположим, у нас есть класс "car 🚗":

class Car {
  constructor({ name, door, material, accelaration }) {
    Object.assign(this, {
      name,
      door,
      material,
      accelaration
    })
  }

  // 起步
  start() {
    console.log('start!')
  }

  // 行驶中
  running() {
    console.log(`${this.name} is running!`)
  }


  // 开门
  open() {
    console.log(`open the ${this.door}`)
  }

  // 加速
  accelerate() {
    console.log(`accelerate with ${this.accelaration}`)
  }
}

class Lamborghini extends Car {
  // Lamborghini路过的时候,拥有很高的回头率,并且会被拍照
  running() {
    console.log(`${this.name} is running!`)
    console.log('girls: "Ahh! Lamborghini is comming!"')
    console.log('boys: "Look! Lamborghini is comming, let us take a photo"')
  }

  // Lamborghini开门的时候,大家都想看看车主究竟是什么样的
  open() {
    console.log(`open the ${this.door}`)
    console.log("who drive this?")
  }

  // Lamborghini加速的时候,巨大的声浪吸引了大家的回头
  accelerate() {
    console.log(`accelerate with ${this.accelaration}`)
    console.log('~~~~~~~~~~~')
    console.log("who's comming?")
  }
}

const o = new Lamborghini({ name: 'Aventador', door: 'scissors door',  material: 'carbon', accelaration: '3s 0-100'  });
o.start();
o.running();
o.accelerate();
o.open();

Есть еще один класс скорой помощи


class ambulance extends Car {
  // 救护车路过的时候,大家会让开
  running() {
    console.log(`${this.name} is running!`)
    console.log('bi~bu~, bi~bu~')
    console.log('ambulance is comming, please go aside')
  }
  // 救护车开门的时候,医生会下来拯救伤员
  open() {
    console.log(`open the ${this.door}`)
    console.log("Are you ok?")
  }
  // 救护车加速的时候,没什么特别的
}
const c = new ambulance({ name: 'ambulance1', door: 'normal door',  material: 'normal', accelaration: 'normal'  });
c.start();
c.running();
c.accelerate();
c.open();

AOP

АОП — это программирование, ориентированное на аспекты и точечные разрезы.Нам нужно найти аспект и точечный разрез, а также внедрить различные функции до и после точечного разреза, чтобы добиться повторного использования кода по горизонтали.

Если два вышеупомянутых подкласса изменены на реализацию АОП, как это сделать? Прежде всего, мы можем обнаружить, что разница между каждым подклассом заключается только в модификации метода родительского класса. Например, открытый метод:

// Lamborghini类open的时候
    console.log(`open the ${this.door}`)
    console.log("who drive this?")

// ambulance类open的时候
    console.log(`open the ${this.door}`)
    console.log("Are you ok?")

сначалаopen the ${this.door}, то на основе АОП точка отсечения равнаopen the ${this.door}Мы хотимopen the doorПосле вставки разницы в поведении:

function injectLamborghini(target) {
  const { open } = target.prototype
  target.prototype.open = function() {
    open.call(this) // 公共特性open,也是切点
    console.log("who drive this?") // 这就是差异性的行为
  }
  return target
}

Точно так же мы внедряем другие различные функции в подкласс, который наследует родительский класс, который является новым подклассом:

function injectLamborghini(target) {
  const { open, running, accelerate } = target.prototype
  target.prototype.open = function() {
    open.call(this) // 切点
    console.log("who drive this?")
  }
  target.prototype.running = function() {
    running.call(this) // 切点
    console.log('girls: "Ahh! Lamborghini is comming!"')
    console.log('boys: "Look! Lamborghini is comming, let us take a photo"')
  }
  target.prototype.accelerate = function() {
    accelerate.call(this) // 切点
    console.log('~~~~~~~~~~~')
    console.log("who's comming?")
  }
  return target
}
const injectLamborghiniSubClass = injectLamborghini(class extends Car{})
const o = new injectLamborghiniSubClass({ name: 'Aventador', door: 'scissors door',  material: 'carbon', accelaration: '3s 0-100'  })
o.start();
o.running();
o.accelerate();
o.open();

// injectLamborghiniSubClass可以使用装饰器语法:
// 需要babel,可以去自己的项目里面试一下
@injectLamborghini
class Lamborghini extends Car{}

Что касается того, как изменить класс скорой помощи на стиль АОП, я считаю, что каждый должен знать

Приложение в реакции

Избегайте setState для несмонтированных компонентов

Асинхронный запрос, когда запрос возвращается, получает данные немедленно, установить и заменить компонент загрузки, очень обычную работу. Однако, когда компонент, который нуждается в SetState, не загружается (переключающие маршруты, разгрузка предыдущего компонента состояния), чтобы SetState будет предупреждать:

Если мы хотим решить эту проблему, нам нужно изменить код при монтировании, размонтировании и запросе

// 挂载
componentDidMount() {
  this._isMounted = true;
}
// 卸载
componentWillUnmount() {
   this._isMounted = false;
}
// 后面请求的时候
request(url)
.then(res => {
  if (this._isMounted) {
    this.setState(...)
  }
})

Это может быть реализовано с помощью HOC, или внедрение кода в стиле АОП может быть реализовано на основе декораторов. Окончательное выражение использования декоратора заключается в том, что если вам нужен этот компонент с функцией «не устанавливать состояние на выгруженном компоненте», вы можете добавить декоратор:


function safe(target) {
  const {
    componentDidMount,
    componentWillUnmount,
    setState,
  } = target.prototype;
  target.prototype.componentDidMount = function() {
    componentDidMount.call(this); // 挂载的切点
    this._isMounted = true;
  }

  target.prototype.componentWillUnmount = function() {
    componentWillUnmount.call(this);// 卸载的切点
    this._isMounted = false;
  }

  target.prototype.setState = function(...args) {
    if (this._isMounted) { // 让setstate只能在挂载后的元素进行
      setState.call(this, ...args); // setstate的切点
    }
  } 
}

// 使用的时候,只需要加一个safe的装饰器
@safe
export default class Test extends Component {
 // ...
}

Использование в функциональных компонентах

Внутреннее состояние функциональных компонентов поддерживается хуками, и с помощью хуков можно имитировать различное поведение, подобное компонентам класса. И это тенденция, что в будущем весь проект будет наполнен функциональными компонентами Как использовать АОП без классов?

На самом деле хуки рождаются с оттенком стиля АОП. Напишите немного логики и инкапсулируйте ее в пользовательский хук. Когда вам нужно его использовать, вы можете вставить хук в функциональный компонент.

Если мы хотим повторно использовать код, основанный на АОП, в функциональных компонентах, прежде всего, нам нужно четко указать, где находится pointcut. Во-вторых, нам нужно внедрить другой код до и после pointcut. Простейшей реализацией является использование модели публикации-подписки для внедрения новой логики в pointcut.

// 自定义一个hook
function useAOP(opts = {}) {
  const store = useRef({
    ...opts,
    ?trigger(key, ...args) {
      if (store[key]) {
        store[key].apply(null, args);
      }
    }
  }).current;
  return store.?trigger;
}

// 函数组件
function Test(props) {
  const trigger = useAOP({
    mount() {
      console.log("did mount");
    },
    click() {
      console.log('click')
    }
  });
  useEffect(() => {
   // 切点是组件挂载
    trigger("mount");
  }, [trigger]); // trigger肯定是每次都一样的,只会执行一次这个effect
// 切点是点击的时候
  return <div onClick={() =>  trigger('click')}>1</div>;
}

Приведенная выше реализация может поддерживать ситуацию, зависящую от внутреннего состояния компонента. Если нам не нужно полагаться на внутреннее состояние компонента, то мы можем напрямую обернуть функцию снаружи и внедрить триггер в свойства:

function createAOP(opts = {}) {
  const store = {
    ...opts,
    ?trigger(key, ...args) {
      if (store[key]) {
        store[key].apply(null, args);
      }
    }
  };
  return function(cpn) {
    return function(...args) {
      const props = args.shift(); // 给props注入trigger
      // 注意,不能直接赋值哦,只能传一个新的进去
      return cpn.apply(null, [
        { ...props, ?trigger: store.?trigger },
        ...args
      ]);
    };
  };
}

// 函数组件Test
function Test(props) {
  const { ?trigger: trigger } = props;
  useEffect(() => {
   // 切点是组件挂载
    trigger("mount");
  }, [trigger]); // trigger肯定是每次都一样的,只会执行一次这个effect
// 切点是点击的时候
  return <div onClick={() =>  trigger('click')}>1</div>;
}

// 用的时候就用这个了
export default createAOP({
  mount() {
    console.log("did mount");
  },
  click() {
    console.log("click");
  }
})(Test)

Пример сценария приложения

Если есть две страницы, структура страницы совершенно другая, но есть несколько интерфейсов и логика обработки данных точно так же (дополнения и удаления)

// 有两个页面,操作的时候,请求的接口方法一样

class A extends Component {
  state = {
    list: [{ info: "info1" }, { info: "info2" }]
  };
  add = () => {}
  del = (index) => {}
  edit = (index) => {}
  render() {
    // 删除和修改的时候传index进去处理某项数据
    return (
      <main>
        <button onClick={this.add}>新增</button>
        <ul>
          {this.state.list.map(({ info }, index) => (
            <li>
              <a onClick={this.del.bind(this, index)}>删除</a>
              <a onClick={this.edit.bind(this, index)}>修改</a>
              <h2>{info}</h2>
            </li>
          ))}
        </ul>
      </main>
    );
  }
}

class B extends Component {
  state = {
    list: [{ info: "不一样的信息" }, { info: "不一样的ui" }]
  };
  add = () => {}
  del = (index) => {}
  edit = (index) => {}
  render() {
    // 新增就新增,删除和修改的时候传index进去处理某项数据
    return (
      <section>
        {this.state.list.map(({ info }, index) => (
          <p>
            <span onClick={this.del.bind(this, index)}>del</span>
            <a onClick={this.edit.bind(this, index)}>edit</a>
            <footer>{info}</footer>
          </p>
        ))}
        <a onClick={this.add}>+</a>
      </section>
    );
  }
}

// 伪代码
function injectOperation(target) {
  target.prototype.add = function(...args) {
    // do something for this.state
    request('/api/add', {
      params: {
        // ...
      }
    }).then(r => { // this已经绑的了,对state做一些事情 })
  }
  target.prototype.edit = function() {} // 类似的
  target.prototype.del = function() {}
  return target;
}

// example,组件内部不再需要写add、edit、del函数
@injectOperation
class A extends Component {}

Никакая общественная озабоченность «не тот же интерфейс», чтобы узнать другую точку зрения на интерфейс, быстрорастущий, играющий с новейшими технологиями, исследующий черный и