Hyperapp에서 보기 구성 요소의 DOM 요소 작업

Hyperapp의 빠르고 선언적인 가상 DOM 엔진은 대부분의 경우 DOM을 제어할 수 있는 훌륭한 방법입니다. 그러나 실제 DOM으로 직접 작업해야 하는 경우가 가끔 있습니다. 문제 없습니다. 기본 브라우저 API를 사용하여 필요에 따라 효과 또는 구독에 포함된 요소를 찾고 조작하기만 하면 됩니다.

그러나 때때로 나는 구성 요소 정의 자체 내에서 보기 구성 요소의 요소에 액세스할 수 있기를 바라고 있습니다. 그렇게 하면 상태, 작업, 효과 및 구독이 더 복잡해져 이미 삐걱거리는 앱에 부담을 주지 않고 구성 요소의 동작을 캡슐화할 수 있습니다. React's useRef -hook에 해당하는 일종의 Hyperapp이 있었으면 합니다.

결과적으로 Hyperapp의 지원되지 않는 구현 세부 정보(즉, 해킹)를 사용하는 것이 가능합니다. 방법은 다음과 같습니다.

마법 속성



먼저 Object.defineProperty를 상기시켜 드리겠습니다. 다른 많은 용도 중에서 객체의 속성을 getset 함수 쌍으로 정의할 수 있습니다. 이는 액세스되거나 할당될 때 일부 코드를 실행하는 속성을 정의할 수 있음을 의미합니다.


const obj = {}

let normalVal
Object.defineProperty(obj, 'normal', {
  get () { return normalVal },
  set (val) { normalVal = val }
})

Object.defineProperty(obj, 'wacky', {
  get () { return -99 },
  set (val) { console.log(val + ' ... really?!') } 
})

obj.normal = 42
obj.normal     // is 42
obj.wacky = 42 // logs "42 ... really?!"
obj.wacky      // is always -99 regardless


나에게 당신의 비밀을 숨길 수 없습니다, Hyperapp!



이제 Hyperapp의 소스 코드를 살펴보십시오. 구체적으로 this line

return (vdom.node = node)


... 그리고 this line

return (newVNode.node = node)


패치 프로세스의 두 위치에서 하이퍼앱은 node 속성을 할당하여 vnode를 변경합니다. 편리하게 할당되는 것은 vnode에 해당하는 DOM 요소입니다.

VNode 데코레이터



이는 다음과 같이 vnode에 대한 데코레이터를 만들 수 있음을 의미합니다.


const withElement = (vnode, fn) => {
  let element
  Object.defineProperty(vnode, 'node', {
    get () { return element },
    set (e) {
      element = e
      fn(element)
    }
  })
  return vnode
}



그리고 입력 및 버튼이 있는 구성 요소인 useRef에 대해 React 문서에서 사용하는 예제를 해결할 수 있습니다. 버튼을 클릭하면 관련 입력이 집중됩니다.

const TextInputWithFocusButton = () => withElement(
  h('span', {}, [
    h('input', {type: 'text'}),
    h('button', {}, text('Focus input')),
  ]),
  span => {
    let [input, button] = span.childNodes
    button.addEventListener('click', () => input.focus())
  }
)


우리가 실제 DOM 요소로 작업하는 함수("요소 프로세서"라고 부름)는 보기에서 구성 요소가 사용되는 각 인스턴스에 대해 자체 해당 범위 요소를 입력으로 사용하여 호출됩니다.

주기를 낭비하지 마십시오



뷰가 패치될 때마다 요소 프로세서가 호출되기 때문에 완벽하지는 않습니다. 실제로 요소가 처음 생성될 때 두 번입니다. 포커스를 입력하기 위해 버튼 클릭을 연결한 후에는 해당 범위에 대해 다시 수행할 필요가 없습니다. 그래서 한 번만 발생하도록 가드를 추가합니다.

span => {
  // make sure we only do this once in the lifetime of
  // each component like this:
  if (span._seen) return
  span._seen = true
  let [input, button] = span.childNodes
  button.addEventListener('click', () => input.focus())
}


주요 중요성:



이 구성 요소가 올바르게 작동하려면 Hyperapp이 동일한 요소를 계속 연결하는 것이 중요합니다. 구성 요소 루트 가상 노드에 key property을 추가하지 않으면 보장되지 않습니다.

const TextInputWithFocusButton = (key) => withElem(
  h('span', {key}, [
  ...


Try the full example live here !

Real-DOM-land에서 액션을 보내시겠습니까?



당신은 여전히 ​​궁금할 것입니다: DOM-랜드에서 하이퍼-랜드로 어떻게 돌아갈 수 있습니까? 요소 처리 기능에서 어떻게 작업을 발송할 수 있습니까?

첫째, 그렇게 해야 하는 경우는 매우 드뭅니다. (기술적으로는 이 중 어느 것도 필요하지 않지만 특히 저것은 필요하지 않습니다)

둘째: 무한 루프가 발생하지 않도록 주의하십시오. 디스패치 액션은 뷰가 패치되도록 하는 상태를 변경할 수 있으며, 이로 인해 요소 프로세서 함수가 다시 호출되고 액션을 다시 디스패치하는 등의 작업이 수행됩니다.

이러한 주의 사항은 다음과 같이 수행합니다. 가상 노드에서 구성된 이벤트에 작업을 바인딩하기만 하면 됩니다. 요소 프로세서에서 구성된 이벤트를 전달합니다. 케이크 한 조각 :)

const TextInputWithFocusButton = ({id, OnButtonFocus}) => withElement(
  h('span', {key: id, onbuttonfocus: OnButtonFocus}, [
    h('input', {type: 'text'}),
    h('button', {}, text('Focus input')),
  ]),
  span => {
    if (span._seen) return
    span._seen = true
    let [input, button] = span.childNodes
    button.addEventListener('click', () => {
      span.dispatchEvent(new Event('buttonfocus'))
      input.focus()
    })
  }
)


Here's the live example again , 이번에는 버튼 중심 입력을 맨 위로 이동하는 동작을 사용합니다. (다른 입력에 다른 텍스트를 입력하여 확인)

Note: We need to add a requestAnimationFrame around input.focus() now because when we dispatch the OnButtonFocus it causes the elements to be moved in the DOM. That causes them to lose focus so we wait to focus() until after they have moved.

좋은 웹페이지 즐겨찾기