vanilla JavaScript에서 React 워크플로우 다시 만들기

최근에는 일반적인 JavaScript 애플리케이션을 구축하는 여러 가지 방법을 시도해 왔습니다.내 생각은 React와 유사한 작업 흐름을 얻기 위해 기본적인 React 기능을 다시 만드는 것이다.이것은 나로 하여금 React 응용 프로그램 구조를 유지하는 동시에 바닐라 자바스크립트의 장점을 보존할 수 있게 할 것이다.프로그램이 늘어나면, 코드를 React로 쉽게 옮길 수 있습니다.
이 글의 마지막 부분에서, 나는 어떤 React도 사용하지 않은 상태에서 React 코드와 거의 같은 코드로 계수기 구성 요소를 만드는 방법을 보여 드리겠습니다.그림과 같이
import * as elements from 'typed-html';
import { notReact } from '../notReact';

const Counter = () => {
  const [count, setCount] = notReact.useState(0);

  const increaseCounter = () => {
    setCount(count+1);
  }
  notReact.addOnClick("increaseCount", increaseCounter);

  let isHigherThan5: string;
  notReact.useEffect(()=>{
    isHigherThan5 =  count > 5 ? "Yes" : "No";
  }, [count, isHigherThan5]);
  return (
    <div>
      <h1>Counter: {count}</h1>
      <button id="increaseCount">Increase count</button>
      <p>Is the count higher than 5? <strong>{isHigherThan5}!</strong></p>
    </div>
  );
}

export default Counter;
저장소here를 찾을 수 있습니다.

설치 프로그램


내가 하는 첫 번째 일은 웹 패키지와 typescript를 설치하는 것이다.내가 typescript를 사용하는 주요 원인은 jsx를 사용하기 쉽기 때문이다. 그렇지 않으면 강제성이 아니다.바벨도 그럴 수 있어.
표준 웹 패키지와 typescript를 설치한 후에 저는 typed-htmlnpm install --save typed-html를 설치했습니다.typescript tsx 파일에서 jsx를 사용할 수 있는 패키지입니다.
설치 후, typescript 설정 파일에 다음 몇 줄을 추가했습니다.
{
  "compilerOptions": {
    "jsx": "react",
    "jsxFactory": "elements.createElement",
  }
}
이 공장은 약간의 제한이 있다.
<foo></foo>; // => Error: Property 'foo' does not exist on type 'JSX.IntrinsicElements'.
<a foo="bar"></a>; // => Error:  Property 'foo' does not exist on type 'HtmlAnchorTag'
우리는 일반적인 React에서처럼 도구와 구성 요소를 사용할 수 없습니다. 반대로 구성 요소는 함수 호출이고 함수 매개 변수는 도구입니다.
지금 jsx공장은 도대체 무엇을 하고 있습니까?
이것은 jsx를 문자열로 변환합니다.이것은 나에게 매우 유용하다. 왜냐하면 나는 간단한 .innerHTML 로 렌더링을 하고 싶기 때문이다.하지만 다른 종류의 제품을 얻고 싶다면 다른 공장을 이용해서 직접 생산할 수도 있다.
jsx를 사용하지 않고 템플릿 텍스트만 사용할 수 있습니다.
인코딩을 시작하기 전에 색인을 만들어야 합니다.html 파일.
/공용/인덱스html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <title>App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <script src="bundle.js"></script>
  </body>
</html>

... 하게 되다


이제 모든 것이 설정되었으니 자바스크립트를 깊이 연구할 때가 되었다.
우선, 나는 notReact.ts라는 파일을 만들어서 /src 폴더에 넣었다.이 파일은 모든 렌더링 및 상태 논리가 있는 위치입니다.
우선, 나는 함수 클립을 하나 만들었는데, 그 안에 두 개의 함수를 넣었다.하나는 초기화, 하나는 렌더링.
export const notReact = (function() {
  let _root: Element;
  let _templateCallback: ITemplateCallback;

  function init(rootElement: Element, templateCallback: ITemplateCallback) {
    _root = rootElement;
    _templateCallback = templateCallback;
    render();
  }
  function render() {
    _root.innerHTML = _templateCallback();
  }

  return {init, render};
})();


type ITemplateCallback = { (): string; }
init()에는 두 개의 매개 변수가 있는데 하나의 루트 요소는 템플릿 용기로 사용되고 다른 리셋 함수는 모든 html을 포함하는 문자열을 되돌려준다.render() 함수는 템플릿 리셋을 호출하여 루트 요소에 분배한다.innerHTML.
다음에 나는 index.tsApp.tsx 파일을 만들고 그것들을 /src 폴더에 넣었다.
그리고 렌더링을 초기화하고 App 파일의 index.ts 구성 요소를 호출합니다.
import App from "./App";
import { notReact } from "./notReact";

const render = () => {
  const root = document.getElementById('root');
  notReact.init(root, App);
}

window.addEventListener("DOMContentLoaded", () => render());
App 구성 요소에서 나는 간단한'Hello World'를 썼다.
import * as elements from 'typed-html';

const App = () => {
  return (
    <h1>
      Hello world;
    </h1>
  );
}

export default App;
결과는 다음과 같습니다.

상태 및 이벤트 탐지기


현재 렌더링이 끝났습니다. 갈고리 useState 를 작성할 때가 되었고, 기본적인 계수기 프로그램을 만들어서 테스트할 때가 되었습니다.
우선, 나는 Counter.tsx 라는 다른 구성 요소를 만들고, 그것을 components 폴더에 넣었다.
내가 이 글을 쓰는 방식은 일반적인 React와 같다. 단지 onClick 사건을 제외하고 나는 지금 생략했다.
import * as elements from 'typed-html';
import { notReact } from '../notReact';

const Counter = () => {
  const [count, setCount] = notReact.useState(0);

  const increaseCounter = () => {
    setCount(count+1);
  }
  return (
    <div>
      <h1>Counter: {count}</h1>
      <button>Increase count</button>
    </div>
  );
}

export default Counter;
그런 다음 애플리케이션 구성 요소를 변경해야 합니다.
import * as elements from 'typed-html';
import Counter from './components/Counter';

const App = () => {
  return (
    <div>
      {Counter()}
    </div>
  );
}

export default App;
모든 것이 준비된 후에useState 갈고리를 작성할 때가 되었다.
export const notReact = (function() {
  let hooks: Array<any> = [];
  let idx: number = 0;

  function useState(initValue: any) {
    let state;
    state = hooks[idx] !== undefined ? hooks[idx] : initValue;
    const _idx = idx;
    const setState = (newValue: any) => {
      hooks[_idx] = newValue;
      render();
    }
    idx++;
    return [state, setState];
  }
  function render() {
    idx=0; //resets on rerender
    ...
  }
  return {useState, init, render};
})();
두 개의 국부 변수가 있다.hooks 라는 그룹 변수로 모든 상태 값을 포함합니다.그리고 idx 변수, 이 변수는 교체hooks 수조에 사용되는 인덱스입니다.useState() 함수 내부에서 매번 useState() 호출할 때마다 하나의 상태 값과setter 함수를 되돌려줍니다.
현재 우리는 일 useState 의 갈고리가 하나 있지만, 우리는 아직 그것을 테스트할 수 없다.우리는 먼저 단추에 onclick 사건 탐지기를 추가해야 한다.문제는 만약에 우리가 이를 jsx에 직접 추가하면 html의 방식을 보여주기 때문에 함수는 정의되지 않을 것이다.
이 문제를 해결하기 위해서 나는 어쩔 수 없이 notReact.ts 파일을 다시 갱신해야 한다.
export const notReact = (function() {
  const _eventArray: IEventArray = [];

  function render() {
    _eventArray.length = 0; //the array gets emptied on rerender
    ...
  document.addEventListener('click', (e) => handleEventListeners(e));
  function handleEventListeners(e: any) {
    _eventArray.forEach((target: any) => {
      if (e.target.id === target.id) {
        e.preventDefault();
        target.callback();
      }
    });
  }
  function addOnClick(id: string, callback: any) {
    _eventArray.push({id, callback});
  }
  return {useState, useEffect, init, render, addOnClick};
})();

type IEventArray = [{id: string, callback: any}] | Array<any>;
나는 eventArray라는 국부 변수를 만들었다.이것은 onclick 이벤트를 가진 모든 요소와 모든 이벤트의 리셋 함수를 포함하는 대상 그룹이다.document에 사건 탐지기가 하나 있다.클릭할 때마다 대상 요소가 이벤트 배열 요소 중 하나인지 확인합니다.만약 그렇다면, 리셋 함수를 시작합니다.
이제 버튼에 onclick 이벤트가 표시되도록 카운터 구성 요소를 업데이트합니다.
const Counter = () => {
  ...
  notReact.addOnClick("increaseCount", increaseCounter);
  ...
  return (
    <div>
      <h1>Counter: {count}</h1>
      <button id="increaseCount">Increase count</button>
    </div>
  );
}
다음은 지금까지의 결과입니다.

부작용


내가 마지막으로 추가한 것은 onclick 연결고리다.
다음은 코드입니다.
export const notReact = (function() {
  let hooks: Array<any> = [];
  let idx: number = 0;

  function useEffect(callback: any, dependancyArray: Array<any>) {
    const oldDependancies = hooks[idx];
    let hasChanged = true;
    if (oldDependancies) {
      hasChanged = dependancyArray.some((dep, i) => !Object.is(dep, oldDependancies[i]));
    }
    hooks[idx] = dependancyArray;
    idx++;
    if (hasChanged) callback();
  }

  return {useState, useEffect, init, render, addOnClick};
})();
마지막으로 렌더링된 종속성이 저장되고 종속성이 변경되었는지 확인합니다.만약 그들이 정말로 바뀌었다면, 리셋 함수는 호출될 것이다.
우리 행동 중에 한번 해 봅시다!카운터가 5보다 높으면 단추 아래에 메시지를 추가했습니다.
다음은 최종 카운터 구성 요소 코드입니다.
import * as elements from 'typed-html';
import { notReact } from '../notReact';

const Counter = () => {
  const [count, setCount] = notReact.useState(0);

  const increaseCounter = () => {
    setCount(count+1);
  }
  notReact.addOnClick("increaseCount", increaseCounter);

  let isHigherThan5: string;
  notReact.useEffect(()=>{
    isHigherThan5 =  count > 5 ? "Yes" : "No";
  }, [count, isHigherThan5]);
  return (
    <div>
      <h1>Counter: {count}</h1>
      <button id="increaseCount">Increase count</button>
      <p>Is the count higher than 5? <strong>{isHigherThan5}!</strong></p>
    </div>
  );
}

export default Counter;

결론


그렇지!이제 구성 요소가 실제 React처럼 보입니다.React로 변경하는 것은 간단합니다. 유일하게 변경해야 할 것은 useEffect 이벤트와 가져오기입니다.
만약 당신이 React에서 일하는 것을 좋아한다면, 이런 방법은 한번 시도해 볼 만할 것이다.이 코드는 개념 검증으로 좋은 테스트를 거치지 않았을 뿐만 아니라 틀림없이 많은 오류가 있을 것이다. 특히 다른 상태가 많을 때이다.이 코드는 매우 큰 개선과 확장 공간을 가지고 있다.그러나 코드가 많지 않기 때문에 프로젝트 수요에 따라 코드를 수정하는 것은 매우 쉽다.더 심각한 프로그램에 대해서는 상태 변경을 동기화하기 위해 이벤트 순환을 실행해야 할 수도 있습니다.
useState와useEffect 갈고리의 실현에 대해 나는 깊이 토론하지 않았다.하지만 더 자세한 것을 알고 싶다면 이것을 보아라. 이것이 바로 내가 이룬 영감이다.
마찬가지로 모든 코드는 this repository에서 찾을 수 있습니다.
읽어주셔서 감사합니다!😁

좋은 웹페이지 즐겨찾기