분류 Reactjs

React Hooks의 힘-React에서 이 기능만 사용하여 앱 만들기

컨텐츠 정보

  • 조회 669 (작성일 )

본문

리액트 훅은 리액트 라이브러리에 새로 추가 된 것으로서 스톰에 의해 리액트 개발자가 널리 채택했습니다. 이러한 후크를 통해 상태 구성 요소를 작성하고 클래스 구성 요소를 작성할 필요 없이 다른 react function을 사용할 수 있습니다. react 후크만 사용하여 나만의 앱을 만들 수 있으며 이는 후크 개념이 react 팀에게 번영하는 전환임을 입증합니다.


https://dev.to/jsmanifest/build-an-app-with-just-react-hooks-2c45 


이 기사에서는 react 후크를 사용하여 Slotify라고 하는 앱을 작성합니다.


Slotify는 사용자에게 사용자 인터페이스를 제공하여 블로그 게시물을 가져와 줄 바꿈 (\ n) 및 단어 개수가 적용된 수량에서 중요한 역할을 하는 따옴표를 넣을 수 있는 텍스트 영역을 사용자에게 제공합니다. 슬롯화 된 게시물에는 최소 1 개의 인용문과 최대 3 개의 인용문이 있습니다.


슬롯을 사용할 수 있는 곳이라면 어디든지 인용문과 인용문 작성자를 삽입 할 수 있습니다. 사용자는 원하는 인용문 / 작성자에서 슬롯과 상호 작용하고 유형 / 붙여 넣기를 할 수 있습니다. 완료되면 저장 버튼을 클릭하면 인용문이 포함 된 리퍼브 블로그 게시물이 다시 작성됩니다. 이것은 사용자가 다음 블로그 게시물로 사용할 최종 버전입니다.


이것들은 우리가 사용할 후크 API입니다. (기본적으로 모두)


이것은 우리가 만들 것입니다 : (블로그 게시물을 스타일 따옴표가 있는 블로그 게시물로 변환하고 스타일이 포함 된 게시물의 HTML 소스 코드를 반환합니다) 


slotify build your app with just react hooks 


더 이상 고민하지 말고 시작합시다!


이 튜토리얼에서는 create-react-app를 사용하여 react 프로젝트를 빠르게 생성 할 것입니다.


(github에서 저장소의 사본을 얻으려면 여기를 클릭하십시오).


계속해서 아래 명령을 사용하여 프로젝트를 만드십시오. 이 튜토리얼에서는 프로젝트 빌드-후크라고합니다.


npx create-react-app build-with-hooks

이제 완료되면 디렉토리로 이동하십시오.


cd build-with-hooks


주요 항목 인 src/index.js 내부에서 앱 구성 요소에 집중할 수 있도록 약간 정리하겠습니다.


import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import './index.css'
import * as serviceWorker from './serviceWorker'
ReactDOM.render(<App />, document.getElementById('root'))
serviceWorker.unregister()

이제 src/App.js로 이동하여 아무것도 렌더링 하지 마십시오.


import React from 'react'

function App() {
  return null
}

export default App


앱의 핵심 기능은 사용자가 따옴표를 삽입 할 수 있도록 블로그 게시물을 특정 유형의 필드에 삽입/작성할 수 있도록 하는 것입니다.


이를 위해 긍정적이고 낙관적인 자세를 유지하려면 먼저 핵심 기능을 해결하여 우리가 올바른 상태임을 알게 하십시오.


즉, 사용자가 버튼을 클릭하여 시작할 수 있도록 먼저 버튼을 만들 것입니다. 그런 다음 사용자가 컨텐츠를 삽입 할 수 있도록 textarea 요소도 작성합니다.


import React from 'react'

function Button({ children, ...props }) {
  return (
    <button type="button" {...props}>
      {children}
    </button>
  )
}

export default Button


index.css 내부에서 모든 버튼이 동일한 스타일을 갖도록 스타일을 적용했습니다.


button {
  border: 2px solid #eee;
  border-radius: 4px;
  padding: 8px 15px;
  background: none;
  color: #555;
  cursor: pointer;
  outline: none;
}

button:hover {
  border: 2px solid rgb(224, 224, 224);
}
button:active {
  border: 2px solid #aaa;
}

textarea 구성 요소를 작성해 보겠습니다. 우리는 그것을 PasteBin이라고 부릅니다.


import React from 'react'

function PasteBin(props) {
  return (
    <textarea
      style={{
        width: '100%',
        margin: '12px 0',
        outline: 'none',
        padding: 12,
        border: '2px solid #eee',
        color: '#666',
        borderRadius: 4,
      }}
      rows={25}
      {...props}
    />
  )
}

export default PasteBin

이제 최종 내용이 생성 될 때 스타일이 포함되기를 원하기 때문에 인라인 스타일을 사용하고 있습니다. 순수 CSS를 사용하면 클래스 이름 문자열 만 생성되므로 구성 요소는 스타일이 없습니다.


우리는 React.useContext를 사용하여 모든 자식 구성 요소가 나머지 구성 요소와 동기화 할 수 있도록 모든 것을 맨 위에서 감싸는 반응 컨텍스트를 만들 것입니다.


Context.js 파일을 작성하십시오.


import React from 'react'

const Context = React.createContext()

export default Context


이제 Context.js를 가져오고 상태를 관리하는 모든 로직을 보유 할 Provider.js를 만들 것입니다.


import React from 'react'
import Slot from './Slot'
import { attachSlots, split } from './utils'
import Context from './Context'

const initialState = {
  slotifiedContent: [],
}

function reducer(state, action) {
  switch (action.type) {
    case 'set-slotified-content':
      return { ...state, slotifiedContent: action.content }
    default:
      return state
  }
}

function useSlotify() {
  const [state, dispatch] = React.useReducer(reducer, initialState)
  const textareaRef = React.useRef()

  function slotify() {
    let slotifiedContent, content
    if (textareaRef && textareaRef.current) {
      content = textareaRef.current.value
    }
    const slot = <Slot />
    if (content) {
      slotifiedContent = attachSlots(split(content), slot)
    }
    dispatch({ type: 'set-slotified-content', content: slotifiedContent })
  }

  return {
    ...state,
    slotify,
    textareaRef,
  }
}

function Provider({ children }) {
  return <Context.Provider value={useSlotify()}>{children}</Context.Provider>
}

export default Provider

이 마지막 코드 스니펫이 매우 중요하므로 수행중인 작업을 설명하고 싶습니다. 우리는 React.useState를 사용하여 상태를 관리했을 것입니다. 그러나 앱이 무엇을 할지 생각할 때 양쪽에서 가져와야 할 상황이 있기 때문에 단일 상태가 아니라는 것을 알 수 있습니다. 고려:

  1. 사용자는 언제 블로그 게시물을 슬롯 화하려고 합니까?
  2. 우리는 언제 그들에게 최종 개조 된 내용을 보여 주어야 합니까?
  3. 블로그 게시물에 몇 개의 슬롯을 삽입해야 합니까?
  4. 슬롯을 언제 표시하거나 숨겨야 합니까?

이를 알기 위해 React.useReducer를 사용하여 상태 업데이트 로직을 단일 위치로 캡슐화하도록 상태를 설계해야 하므로 첫 번째 조치는 'set-slotified-'유형의 조치를 전달하여 액세스 가능한 첫 번째 스위치 케이스를 추가하여 선언됩니다. 함유량'.


블로그 게시물에 슬롯을 삽입하는 방법은 문자열을 가져 와서 줄 바꿈 '\ n'으로 구분하여 배열로 변환하는 것입니다. 이는 초기 상태가 slotifiedContent를 배열로 선언하는 이유입니다. 데이터 입력


또한 앞에서 만든 PasteBin 구성 요소에 대한 참조를 가져 오는 데 사용 해야 하는 textareaRef가 선언되어 있습니다. 우리는 텍스트 영역을 완전히 제어 할 수 있었지만, 가장 쉽고 성능이 좋은 방법은 루트 텍스트 영역 요소에 대한 참조를 얻는 것입니다. 우리가 해야 할 일은 상태를 설정하는 대신 값을 가져 오기 때문입니다. 이것은 나중에 textarea에서 ref prop을 사용하여 얻습니다.


우리의 slotify 기능은 사용자가 Start Quotifying 버튼을 눌러 블로그 게시물을 슬롯화할 때 호출됩니다. 의도는 모달을 팝업하여 인용 / 작성자를 입력 할 수있는 슬롯을 표시하는 것입니다. 우리는 PasteBin 컴포넌트에 대한 참조를 사용하여 텍스트 영역의 현재 값을 잡고 컨텐츠를 모달로 마이그레이션 합니다.


그런 다음 두 가지 유틸리티 기능인 attachSlots와 split을 사용하여 블로그 게시물을 슬롯 화하고 이를 사용하여 UI가 이를 가져 와서 작업을 수행 할 수 있도록 state.slotifiedContent를 설정합니다. 


우리는 attachSlots를 넣고 다음과 같이 utils.js 파일로 분할합니다.


export function attachSlots(content, slot) {
  if (!Array.isArray(content)) {
    throw new Error('content is not an array')
  }
  let result = []
  // Post is too short. Only provide a quote at the top
  if (content.length <= 50) {
    result = [slot, ...content]
  }
  // Post is a little larger but 3 quotes is excessive. Insert a max of 2 quotes
  else if (content.length > 50 && content.length < 100) {
    result = [slot, ...content, slot]
  }
  // Post should be large enough to look beautiful with 3 quotes inserted (top/mid/bottom)
  else if (content.length > 100) {
    const midpoint = Math.floor(content.length / 2)
    result = [
      slot,
      ...content.slice(0, midpoint),
      slot,
      ...content.slice(midpoint),
      slot,
    ]
  }
  return result
}

// Returns the content back as an array using a delimiter
export function split(content, delimiter = '\n') {
  return content.split(delimiter)
}

textareaRef를 PasteBin에 적용하려면 React.useContext를 사용하여 앞에서 useSlotify에서 선언 한 React.useRef 후크를 가져와야 합니다.


import React from 'react'
import Context from './Context'

function PasteBin(props) {
  const { textareaRef } = React.useContext(Context)
  return (
    <textarea
      ref={textareaRef}
      style={{
        width: '100%',
        margin: '12px 0',
        outline: 'none',
        padding: 12,
        border: '2px solid #eee',
        color: '#666',
        borderRadius: 4,
      }}
      rows={25}
      {...props}
    />
  )
}

export default PasteBin


마지막으로 누락 된 것은 <Slot /> 컴포넌트를 컨텍스트 내에서 사용했기 때문에 작성하는 것입니다. 이 슬롯 구성 요소는 사용자가 인용하고 작성하는 구성 요소입니다. 사용자가 Quotifying 시작 버튼을 클릭 할 때만 열리는 모달 구성 요소 안에 넣기 때문에 사용자에게 즉시 표시되지 않습니다.


이 슬롯 구성 요소는 약간 힘들지만 나중에 무슨 일이 일어나고 있는지 설명하겠습니다.


import React from 'react'
import PropTypes from 'prop-types'
import cx from 'classnames'
import Context from './Context'
import styles from './styles.module.css'

function SlotDrafting({ quote, author, onChange }) {
  const inputStyle = {
    border: 0,
    borderRadius: 4,
    background: 'none',
    fontSize: '1.2rem',
    color: '#fff',
    padding: '6px 15px',
    width: '100%',
    height: '100%',
    outline: 'none',
    marginRight: 4,
  }

  return (
    <div
      style={{
        display: 'flex',
        justifyContent: 'space-around',
        alignItems: 'center',
      }}
    >
      <input
        name="quote"
        type="text"
        placeholder="Insert a quote"
        style={{ flexGrow: 1, flexBasis: '70%' }}
        onChange={onChange}
        value={quote}
        className={styles.slotQuoteInput}
        style={{ ...inputStyle, flexGrow: 1, flexBasis: '60%' }}
      />
      <input
        name="author"
        type="text"
        placeholder="Author"
        style={{ flexBasis: '30%' }}
        onChange={onChange}
        value={author}
        className={styles.slotQuoteInput}
        style={{ ...inputStyle, flexBasis: '40%' }}
      />
    </div>
  )
}

function SlotStatic({ quote, author }) {
  return (
    <div style={{ padding: '12px 0' }}>
      <h2 style={{ fontWeight: 700, color: '#2bc7c7' }}>{quote}</h2>
      <p
        style={{
          marginLeft: 50,
          fontStyle: 'italic',
          color: 'rgb(51, 52, 54)',
          opacity: 0.7,
          textAlign: 'right',
        }}
      >
        - {author}
      </p>
    </div>
  )
}

function Slot({ input = 'textfield' }) {
  const [quote, setQuote] = React.useState('')
  const [author, setAuthor] = React.useState('')
  const { drafting } = React.useContext(Context)

  function onChange(e) {
    if (e.target.name === 'quote') {
      setQuote(e.target.value)
    } else {
      setAuthor(e.target.value)
    }
  }

  let draftComponent, staticComponent

  if (drafting) {
    switch (input) {
      case 'textfield':
        draftComponent = (
          <SlotDrafting onChange={onChange} quote={quote} author={author} />
        )
        break
      default:
        break
    }
  } else {
    switch (input) {
      case 'textfield':
        staticComponent = <SlotStatic quote={quote} author={author} />
        break
      default:
        break
    }
  }

  return (
    <div
      style={{
        color: '#fff',
        borderRadius: 4,
        margin: '12px 0',
        outline: 'none',
        transition: 'all 0.2s ease-out',
        width: '100%',
        background: drafting
          ? 'rgba(175, 56, 90, 0.2)'
          : 'rgba(16, 46, 54, 0.02)',
        boxShadow: drafting
          ? undefined
          : '0 3px 15px 15px rgba(51, 51, 51, 0.03)',
        height: drafting ? 70 : '100%',
        minHeight: drafting ? 'auto' : 70,
        maxHeight: drafting ? 'auto' : 100,
        padding: drafting ? 8 : 0,
      }}
    >
      <div
        className={styles.slotInnerRoot}
        style={{
          transition: 'all 0.2s ease-out',
          cursor: 'pointer',
          width: '100%',
          height: '100%',
          padding: '0 6px',
          borderRadius: 4,
          display: 'flex',
          alignItems: 'center',
          textTransform: 'uppercase',
          justifyContent: drafting ? 'center' : 'space-around',
          background: drafting
            ? 'rgba(100, 100, 100, 0.35)'
            : 'rgba(100, 100, 100, 0.05)',
        }}
      >
        {drafting ? draftComponent : staticComponent}
      </div>
    </div>
  )
}

Slot.defaultProps = {
  slot: true,
}

Slot.propTypes = {
  input: PropTypes.oneOf(['textfield']),
}

export default Slot


이 파일에서 가장 중요한 부분은 state.drafting입니다. 문맥 상으로는 이것을 선언하지 않았지만, 그 목적은 사용자에게 슬롯을 보여줄 때와 최종 출력을 보여줄 때를 알 수 있는 방법을 제공하는 것입니다. state.drafting이 true이면 (기본값이 됨), 인용 및 인용 작성자를 삽입 할 수 있는 블록 인 슬롯을 표시합니다. 저장 버튼을 클릭하면 state.drafting이 false로 전환되고 이를 사용하여 최종 출력을 보려는 것으로 결정합니다.


나중에 'textfield'라는 기본값으로 입력 매개 변수를 선언했습니다. 앞으로는 입력 이외의 다른 입력 유형을 사용하여 따옴표를 입력 할 수 있기를 원할 수도 있습니다 (예 : 이미지를 따옴표로 업로드 할 수 있는 파일 입력 등). . 이 튜토리얼에서는 'textfield'만 지원합니다.


따라서 state.drafting이 true이면 슬롯에서 <SlotDrafting />이 사용되고 false이면 <SlotStatic />이 사용됩니다. 이 구별을 별도의 구성 요소로 분리하는 것이 좋으므로 많은 if / else 조건부로 구성 요소를 팽창 시키지 않습니다.


또한 인용 / 작성자 입력 필드에 대해 일부 인라인 스타일을 선언했지만 인라인 스타일로는 수행 할 수 없으므로 플레이스 홀더의 스타일을 지정할 수 있도록 className = {styles.slotQuoteInput}을 계속 적용했습니다. (입력이 생성되지 않기 때문에 최종 리퍼브 콘텐츠에는 괜찮습니다)


이에 대한 CSS는 다음과 같습니다.


.slotQuoteInput::placeholder {
  color: #fff;
  font-size: 0.9rem;
}


되돌아 가서 드 래프팅 상태를 컨텍스트에 선언하겠습니다.


import React from 'react'
import Slot from './Slot'
import { attachSlots, split } from './utils'
import Context from './Context'

const initialState = {
  slotifiedContent: [],
  drafting: true,
}

function reducer(state, action) {
  switch (action.type) {
    case 'set-slotified-content':
      return { ...state, slotifiedContent: action.content }
    case 'set-drafting':
      return { ...state, drafting: action.drafting }
    default:
      return state
  }
}

function useSlotify() {
  const [state, dispatch] = React.useReducer(reducer, initialState)
  const textareaRef = React.useRef()

  function onSave() {
    if (state.drafting) {
      setDrafting(false)
    }
  }

  function setDrafting(drafting) {
    if (drafting === undefined) return
    dispatch({ type: 'set-drafting', drafting })
  }

  function slotify() {
    let slotifiedContent, content
    if (textareaRef && textareaRef.current) {
      content = textareaRef.current.value
    }
    const slot = <Slot />
    if (content && typeof content === 'string') {
      slotifiedContent = attachSlots(split(content), slot)
    }
    dispatch({ type: 'set-slotified-content', content: slotifiedContent })
  }

  return {
    ...state,
    slotify,
    onSave,
    setDrafting,
    textareaRef,
  }
}

function Provider({ children }) {
  return <Context.Provider value={useSlotify()}>{children}</Context.Provider>
}

export default Provider


이제 App.js 컴포넌트에 이것을 넣어서 지금까지의 모습을 볼 수 있습니다 :


(참고 :이 예에서는 모달에 필요하지 않은 semantic-ui-react의 모달 구성 요소를 사용했습니다. 반응 포털 API를 사용하여 모달을 사용하거나 자신의 일반 모달을 만들 수 있습니다).


import React from 'react'
import { Modal } from 'semantic-ui-react'
import Button from './Button'
import Context from './Context'
import Provider from './Provider'
import PasteBin from './PasteBin'
import styles from './styles.module.css'

// Purposely call each fn without args since we don't need them
const callFns = (...fns) => () => fns.forEach((fn) => fn && fn())

const App = () => {
  const {
    modalOpened,
    slotifiedContent = [],
    slotify,
    onSave,
    openModal,
    closeModal,
  } = React.useContext(Context)

  return (
    <div
      style={{
        padding: 12,
        boxSizing: 'border-box',
      }}
    >
      <Modal
        open={modalOpened}
        trigger={
          <Button type="button" onClick={callFns(slotify, openModal)}>
            Start Quotifying
          </Button>
        }
      >
        <Modal.Content
          style={{
            background: '#fff',
            padding: 12,
            color: '#333',
            width: '100%',
          }}
        >
          <div>
            <Modal.Description>
              {slotifiedContent.map((content) => (
                <div style={{ whiteSpace: 'pre-line' }}>{content}</div>
              ))}
            </Modal.Description>
          </div>
          <Modal.Actions>
            <Button type="button" onClick={onSave}>
              SAVE
            </Button>
          </Modal.Actions>
        </Modal.Content>
      </Modal>
      <PasteBin onSubmit={slotify} />
    </div>
  )
}

export default () => (
  <Provider>
    <App />
  </Provider>
)

서버를 시작하기 전에 모달 상태를 선언해야 합니다 (열기 / 닫기).


import React from 'react'
import Slot from './Slot'
import { attachSlots, split } from './utils'
import Context from './Context'

const initialState = {
  slotifiedContent: [],
  drafting: true,
  modalOpened: false,
}

function reducer(state, action) {
  switch (action.type) {
    case 'set-slotified-content':
      return { ...state, slotifiedContent: action.content }
    case 'set-drafting':
      return { ...state, drafting: action.drafting }
    case 'open-modal':
      return { ...state, modalOpened: true }
    case 'close-modal':
      return { ...state, modalOpened: false }
    default:
      return state
  }
}

function useSlotify() {
  const [state, dispatch] = React.useReducer(reducer, initialState)
  const textareaRef = React.useRef()

  function onSave() {
    if (state.drafting) {
      setDrafting(false)
    }
  }

  function openModal() {
    dispatch({ type: 'open-modal' })
  }

  function closeModal() {
    dispatch({ type: 'close-modal' })
  }

  function setDrafting(drafting) {
    if (typeof drafting !== 'boolean') return
    dispatch({ type: 'set-drafting', drafting })
  }

  function slotify() {
    let slotifiedContent, content
    if (textareaRef && textareaRef.current) {
      content = textareaRef.current.value
    }
    const slot = <Slot />
    if (content && typeof content === 'string') {
      slotifiedContent = attachSlots(split(content), slot)
    }
    if (!state.drafting) {
      setDrafting(true)
    }
    dispatch({ type: 'set-slotified-content', content: slotifiedContent })
  }

  return {
    ...state,
    slotify,
    onSave,
    setDrafting,
    textareaRef,
    openModal,
    closeModal,
  }
}

function Provider({ children }) {
  return <Context.Provider value={useSlotify()}>{children}</Context.Provider>
}

export default Provider

그리고 여기까지 우리가 해야 할 것이 있습니다 :


build your app with just react hooks 2019 


(참고 : SAVE 버튼은 이미지에서 모달을 닫고 있지만 사소한 오류였습니다. 모달을 닫아서는 안됩니다)


이제 우리는 PasteBin을 텍스트 영역에 React.useImperativeHandle을 사용하여 새로운 API를 선언하도록 약간 변경하여 useSlotify에서 사용할 수 있도록 하고 많은 함수로 후크를 부 풀리지 않고 대신 캡슐화 된 API를 제공합니다. :


import React from 'react'
import Context from './Context'

function PasteBin(props) {
  const { textareaRef, textareaUtils } = React.useContext(Context)

  React.useImperativeHandle(textareaUtils, () => ({
    copy: () => {
      textareaRef.current.select()
      document.execCommand('copy')
      textareaRef.current.blur()
    },
    getText: () => {
      return textareaRef.current.value
    },
  }))

  return (
    <textarea
      ref={textareaRef}
      style={{
        width: '100%',
        margin: '12px 0',
        outline: 'none',
        padding: 12,
        border: '2px solid #eee',
        color: '#666',
        borderRadius: 4,
      }}
      rows={25}
      {...props}
    />
  )
}

export default PasteBin


textareaUtils는 useSlotify 후크에서 textareaRef 바로 옆에 배치되는 React.useRef입니다.


const [state, dispatch] = React.useReducer(reducer, initialState)
const textareaRef = React.useRef()
const textareaUtils = React.useRef()


slotify 함수에서 이 새로운 API를 사용할 것입니다 :


function slotify() {
  let slotifiedContent, content
  if (textareaRef && textareaRef.current) {
    textareaUtils.current.copy()
    textareaUtils.current.blur()
    content = textareaUtils.current.getText()
  }
  const slot = <Slot />
  if (content && typeof content === 'string') {
    slotifiedContent = attachSlots(split(content), slot)
  }
  if (!state.drafting) {
    setDrafting(true)
  }
  dispatch({ type: 'set-slotified-content', content: slotifiedContent })
}


다음으로 해야 할 일은 사용자가 슬롯을 보고 있을 때 아직 저자를 삽입하지 않은 것을 감지하면 해당 요소를 플래시 하여 더 많은 관심을 끌 수 있다는 것입니다. 


이를 위해 SlotDrafting에 작성자 입력이 포함되어 있으므로 SlotDrafting 구성 요소 내부에서 React.useLayoutEffect를 사용합니다.


function SlotDrafting({ quote, author, onChange }) {
  const authorRef = React.createRef()

  React.useLayoutEffect(() => {
    const elem = authorRef.current
    if (!author) {
      elem.classList.add(styles.slotQuoteInputAttention)
    } else if (author) {
      elem.classList.remove(styles.slotQuoteInputAttention)
    }
  }, [author, authorRef])

  const inputStyle = {
    border: 0,
    borderRadius: 4,
    background: 'none',
    fontSize: '1.2rem',
    color: '#fff',
    padding: '6px 15px',
    width: '100%',
    height: '100%',
    outline: 'none',
    marginRight: 4,
  }

  return (
    <div
      style={{
        display: 'flex',
        justifyContent: 'space-around',
        alignItems: 'center',
      }}
    >
      <input
        name="quote"
        type="text"
        placeholder="Insert a quote"
        onChange={onChange}
        value={quote}
        className={styles.slotQuoteInput}
        style={{ ...inputStyle, flexGrow: 1, flexBasis: '60%' }}
      />
      <input
        ref={authorRef}
        name="author"
        type="text"
        placeholder="Author"
        onChange={onChange}
        value={author}
        className={styles.slotQuoteInput}
        style={{ ...inputStyle, flexBasis: '40%' }}
      />
    </div>
  )
}


여기서는 useLayoutEffect를 사용할 필요가 없었지만 데모 용일 뿐입니다. 스타일 업데이트에 좋은 옵션으로 알려져 있습니다. dom이 마운트 된 후 후크가 호출되고 돌연변이가 업데이트 되었기 때문입니다. 스타일링에 유리한 이유는 다음 브라우저 다시 그리기 전에 호출되기 때문입니다. 반면에 useEffect 후크는 나중에 호출되므로 UI에서 번쩍 거리는 효과가 발생할 수 있습니다.


스타일 :


.slotQuoteInputAttention {
  transition: all 1s ease-out;
  animation: emptyAuthor 3s infinite;
  border: 1px solid #91ffde;
}

.slotQuoteInputAttention::placeholder {
  color: #91ffde;
}

.slotQuoteInputAttention:hover,
.slotQuoteInputAttention:focus,
.slotQuoteInputAttention:active {
  transform: scale(1.1);
}

@keyframes emptyAuthor {
  0% {
    opacity: 1;
  }
  50% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}


모달의 맨 아래에는 useSlotify에서 onSave를 호출하는 SAVE 버튼이 있습니다. 사용자가 이 버튼을 클릭하면 슬롯이 완성 된 슬롯으로 변환됩니다 (제도 === false 일 때). 또한 HTML 소스 코드를 클립 보드에 복사하여 블로그 게시물에 내용을 붙여 넣을 수 있는 버튼을 근처에 렌더링 합니다.


지금까지 우리가 가진 것은 다음과 같습니다.


CSS 클래스 이름으로 작업하는 것을 제외하고는 모든 것이 동일하게 유지됩니다. 새로운 CSS 클래스 이름의 경우 정적 접미사가 붙어서 === false를 제도 할 때 사용됨을 나타냅니다. CSS 변경 사항을 수용하기 위해 Slot 구성 요소를 약간 변경했습니다.


function Slot({ input = 'textfield' }) {
  const [quote, setQuote] = React.useState('')
  const [author, setAuthor] = React.useState('')
  const { drafting } = React.useContext(Context)

  function onChange(e) {
    if (e.target.name === 'quote') {
      setQuote(e.target.value)
    } else {
      setAuthor(e.target.value)
    }
  }

  let draftComponent, staticComponent

  if (drafting) {
    switch (input) {
      case 'textfield':
        draftComponent = (
          <SlotDrafting onChange={onChange} quote={quote} author={author} />
        )
        break
      default:
        break
    }
  } else {
    switch (input) {
      case 'textfield':
        staticComponent = <SlotStatic quote={quote} author={author} />
        break
      default:
        break
    }
  }

  return (
    <div
      style={{
        color: '#fff',
        borderRadius: 4,
        margin: '12px 0',
        outline: 'none',
        transition: 'all 0.2s ease-out',
        width: '100%',
        background: drafting
          ? 'rgba(175, 56, 90, 0.2)'
          : 'rgba(16, 46, 54, 0.02)',
        boxShadow: drafting
          ? undefined
          : '0 3px 15px 15px rgba(51, 51, 51, 0.03)',
        height: drafting ? 70 : '100%',
        minHeight: drafting ? 'auto' : 70,
        maxHeight: drafting ? 'auto' : 100,
        padding: drafting ? 8 : 0,
      }}
      className={cx({
        [styles.slotRoot]: drafting,
        [styles.slotRootStatic]: !drafting,
      })}
    >
      <div
        className={styles.slotInnerRoot}
        style={{
          transition: 'all 0.2s ease-out',
          cursor: 'pointer',
          width: '100%',
          height: '100%',
          padding: '0 6px',
          borderRadius: 4,
          display: 'flex',
          alignItems: 'center',
          textTransform: 'uppercase',
          justifyContent: drafting ? 'center' : 'space-around',
          background: drafting
            ? 'rgba(100, 100, 100, 0.35)'
            : 'rgba(100, 100, 100, 0.05)',
        }}
      >
        {drafting ? draftComponent : staticComponent}
      </div>
    </div>
  )
}


새로 추가 된 CSS 스타일은 다음과 같습니다.


.slotRoot:hover {
  background: rgba(245, 49, 104, 0.3) !important;
}

.slotRootStatic:hover {
  background: rgba(100, 100, 100, 0.07) !important;
}

.slotInnerRoot:hover {
  filter: brightness(80%);
}


다음은 앱의 모습입니다.


slotify2 


마지막으로 해야 할 일은 닫기 버튼을 추가하여 모달을 닫고, 복사 버튼은 최종 블로그 게시물의 소스 코드를 복사하는 것입니다.


닫기 버튼을 추가하는 것은 쉽습니다. 저장 버튼 옆에 이 버튼을 추가하십시오. 복사 버튼은 닫기 버튼 옆에 위치합니다. 이 버튼에는 몇 가지 onClick 핸들러가 제공됩니다.


<Modal.Actions>
  <Button type="button" onClick={onSave}>
    SAVE
  </Button>
  &nbsp;
  <Button type="button" onClick={closeModal}>
    CLOSE
  </Button>
  &nbsp;
  <Button type="button" onClick={onCopyFinalDraft}>
    COPY
  </Button>
</Modal.Actions>


onCopyFinalContent 함수를 구현할 때 수행해야 하지만 아직 그렇지 않습니다. 마지막 단계가 누락되었습니다. 최종 컨텐츠를 복사 할 때 UI의 어떤 부분을 복사합니까? 블로그 게시물에 SAVE, CLOSE 및 COPY 버튼을 원하지 않거나 어색하게 보이기 때문에 전체 모달을 복사 할 수 없습니다. 우리는 또 다른 React.useRef를 만들고 그것을 사용하여 원하는 내용 만 포함하는 특정 요소에 첨부해야 합니다.


이것이 우리가 스타일을 리퍼브 버전에 포함 시키기를 원하기 때문에 CSS 클래스가 아닌 인라인 스타일을 사용하는 이유입니다.


사용중인 modalRef 선언


const textareaRef = React.useRef()
const textareaUtils = React.useRef()
const modalRef = React.useRef()


내용 만 포함 할 요소에 첨부하십시오.


const App = () => {
  const {
    modalOpened,
    slotifiedContent = [],
    slotify,
    onSave,
    openModal,
    closeModal,
    modalRef,
    onCopyFinalContent,
  } = React.useContext(Context)

  const ModalContent = React.useCallback(
    ({ innerRef, ...props }) => <div ref={innerRef} {...props} />,
    [],
  )

  return (
    <div
      style={{
        padding: 12,
        boxSizing: 'border-box',
      }}
    >
      <Modal
        open={modalOpened}
        trigger={
          <Button type="button" onClick={callFns(slotify, openModal)}>
            Start Quotifying
          </Button>
        }
        style={{
          background: '#fff',
          padding: 12,
          color: '#333',
          width: '100%',
        }}
      >
        <Modal.Content>
          <Modal.Description as={ModalContent} innerRef={modalRef}>
            {slotifiedContent.map((content) => (
              <div style={{ whiteSpace: 'pre-line' }}>{content}</div>
            ))}
          </Modal.Description>
          <Modal.Actions>
            <Button type="button" onClick={onSave}>
              SAVE
            </Button>
            &nbsp;
            <Button type="button" onClick={closeModal}>
              CLOSE
            </Button>
            &nbsp;
            <Button type="button" onClick={onCopyFinalContent}>
              COPY
            </Button>
          </Modal.Actions>
        </Modal.Content>
      </Modal>
      <PasteBin onSubmit={slotify} />
    </div>
  )
}


참고 : 참조를 동일하게 유지하기 위해 ModalContent를 React.useCallback으로 래핑했습니다. 그렇지 않으면 onSave 함수가 상태를 업데이트하므로 구성 요소가 다시 렌더링 되고 모든 따옴표 / 저자 값이 재설정 됩니다. 상태가 업데이트되면 ModalContent가 다시 생성되어 원하지 않는 새로운 빈 상태를 만듭니다.


마지막으로 onCopyFinalDraft는 modalRef 참조를 사용하는 useSlotify 후크 안에 배치됩니다.


function onCopyFinalContent() {
  const html = modalRef.current.innerHTML
  const inputEl = document.createElement('textarea')
  document.body.appendChild(inputEl)
  inputEl.value = html
  inputEl.select()
  document.execCommand('copy')
  document.body.removeChild(inputEl)
}


그리고 우리는 끝났습니다!


build your web app with just react hooks 2019 


결론 


이것으로 이 포스트의 끝이 끝납니다! 도움이 되었기를 바라며 앞으로 더 많은 정보를 찾아 보시기 바랍니다.