Tepki Kancalarının Gücü – JSManifest

Tepki Kancalarının Gücü – JSManifest

Tepki kancalar React kitaplığına yeni bir ektir ve React geliştiricilerini fırtınalar estirmiştir. Kancalar, bir durum mantığı yazmanıza ve diğer React özelliklerini kullanmanıza izin verir. sınıf bileşeni. Team React’teki herkes için sismik bir değişim olan Hooks’u tek başına kullanarak kendi uygulamalarınızı yapabilirsiniz.

Bu yazıda, sadece React Hooks kullanarak “Slotify” adını vereceğim bir uygulama geliştireceğiz.

Slotify Ne Yapar ve Nasıl Yapar?

Slotify sunan bir kullanıcı arayüzü sağlayacaktır. metin alanı herhangi bir blog gönderisine alıntı eklemek için. Yeni satırlar (n) ve kelime sayısı, miktarda rol oynayacaktır. “Slotified” bir gönderide en az bir ve en fazla üç alıntı olacaktır. Bir yuvanın bulunduğu her yere bir teklif eklenebilir. Kullanıcı, slot ile etkileşime girebilir ve kendi seçtikleri bir alıntı ve yazar atıfını yazabilir veya yapıştırabilir. Bitirdiklerinde, kaydet düğmesini tıklayabilirler ve blog gönderisi şimdi alıntıları da dahil olmak üzere yeniden yüklenecektir.

Bunlar kullanacağımız kanca apileri: (Temelde hepsi)

İnşa edeceğimiz şey budur: (Bir blog gönderisini stilize edilmiş alıntılarla bir blog gönderisine dönüştürür ve gönderilerin stilleri içeren bir HTML kaynak kodunu geri döndürür)

Başlayalım

Bu derste, create-react-app ile hızlı bir şekilde bir tepki projesi oluşturacağız.

(Deponun bir kopyasını github’dan almak istiyorsanız, tıklayın burada).

Devam edin ve aşağıdaki komutu kullanarak bir proje oluşturun. Bu eğitim için projemizi arayacağım kancalarla inşa.

npx create-react-app build-with-hooks

Şimdi bittiğinde dizine gidin:

Ana girişin içinde src/index.js biraz temizleyeceğiz, böylece konuya odaklanabiliriz. App bileşen:

kaynak/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()

şimdi git src/App.js ve hiçbir şey oluşturma ile başlayalım:

import React from 'react'

function App() {
  return null
}

export default App

Uygulamamızın temel işlevi, bir kullanıcının bir tür alana bir blog yazısı eklemesine/yazmasına izin vermektir, böylece alıntılar eklenebilir.

Bunu yapabileceğimize dair olumlu ve iyimser kaldığımızdan emin olmak için, iyi durumda olduğumuzu bilmemiz için önce temel işlevleri ele alalım.

Bu, kullanıcının tıklayarak başlatabilmesi için önce bir düğme oluşturacağımız anlamına gelir. Ardından, biz de oluşturacağız textarea kullanıcının içerik ekleyebilmesi için öğe.

kaynak/Button.js

import React from 'react'

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

export default Button

index.css içinde biraz stil uyguladım, böylece her button aynı stillere sahip olacak:

kaynak/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;
}

Şimdi textarea bileşenini oluşturmaya devam edelim. arayacağız PasteBin:

kaynak/PasteBin.js

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

Nihai içerik oluşturulurken stillerin dahil edilmesini istediğimiz için satır içi stiller kullanıyoruz. Saf CSS kullanırsak, yalnızca sınıf adı dizeleri oluşturulur, böylece bileşenler stilsiz olur.

Bir tepki yaratacağız bağlam tüm alt bileşenleri, bileşenlerin geri kalanıyla senkronize kalmaya zorlamak için tüm bunları baştan sarmak. Bunu kullanarak yapacağız React.useContext

Oluşturmak Context.js dosya:

kaynak/bağlam.js

import React from 'react'

const Context = React.createContext()

export default Context

Şimdi oluşturacağız Provider.js hangisi ithal edecek Context.js ve tüm mantığı yönetim durumunda tutacaktır:

kaynak/Sağlayıcı.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

Bu son kod parçacığı çok önemlidir. biz kullanırdık React.useState devletimizi yönetmek için, ancak uygulamamızın ne yapacağını düşündüğünüzde, bunun sadece tek bir devlet olmadığını fark edebilirsiniz. Bunun nedeni, her iki taraftan da dikkate alınması gereken durumlar olmasıdır:

  1. Kullanıcı blog gönderisini ne zaman bölmek istiyor?
  2. Onlara nihai, yenilenmiş içeriği ne zaman göstermeliyiz?
  3. Blog gönderisine kaç slot eklemeliyiz?
  4. Slotları ne zaman göstermeli veya gizlemeliyiz?

Bunu bilerek, bir kullanmamız gerekir. React.useReducer durum güncelleme mantığını tek bir konuma yerleştirmek için durumumuzu tasarlamak. İlk eylemimiz, type ile bir eylem göndererek erişilebilen ilk anahtar vakasını ekleyerek bildirildi. 'set-slotified-content'.

Blog gönderisine yuva eklemenin yolu, bir dize alıp onu yeni satırlarla sınırlayan bir diziye dönüştürmektir. 'n' bu yüzden ilk durum bildiriyor slotifiedContent dizi olarak, çünkü çalışma verilerimizi oraya koyacağız.

Ayrıca bir görüyoruz textareaRef referansımızı almak için kullanmamız gerektiği bildirildi. PasteBin Daha önce oluşturduğumuz bileşen. Textarea’yı tamamen yapabilirdik kontrollüancak bununla iletişim kurmanın en kolay ve en performanslı yolu, sadece köke bir referans almaktır. textarea element çünkü tek yapmamız gereken durumları ayarlamak yerine değerini almak. Bu, kullanımdan yakalanacak ref desteklemek textarea sonra.

Bizim slotify kullanıcı düğmeye bastığında işlev çağrılır. Alıntılamaya Başla düğmesini tıklayın. Amaç, bir modal açmak ve onlara alıntılarını/yazarlarını girebilecekleri yuvaları göstermektir. referansını kullanıyoruz PasteBin textarea’nın geçerli değerini almak ve içeriği modala taşımak için bileşen.

Daha sonra iki yardımcı fonksiyon kullanırız, attachSlots ve split blog gönderisini bölmek ve bunu ayarlamak için kullanmak için state.slotifiedContent böylece kullanıcı arayüzümüz onu alıp işlerini yapabilir.

Koyduk attachSlots ve split içine utils.js dosya şu şekilde:

kaynak/utils.js

export function attachSlots(content, slot) {
  if (!Array.isArray(content)) {
    throw new Error('content is not an array')
  }
  let result = []
  
  if (content.length <= 50) {
    result = [slot, ...content]
  }
  
  else if (content.length > 50 && content.length < 100) {
    result = [slot, ...content, slot]
  }
  
  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
}


export function split(content, delimiter = 'n') {
  return content.split(delimiter)
}

uygulamak için textareaRef için PasteBinkullanmak zorundayız React.useContext almak için React.useRef daha önce ilan ettiğimiz kanca useSlotify:

kaynak/PasteBin.js

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

Kaybettiğimiz son şey, <Slot /> bileşen, çünkü onu bağlamımız içinde kullandık. Bu slot bileşeni, kullanıcıdan bir alıntı ve yazar alan bileşendir. Bu, kullanıcı tarafından hemen görülmeyecektir, çünkü onu yalnızca kullanıcı tıkladığında açılacak olan kalıcı bileşenin içine koyacağız. Alıntılamaya Başla buton.

Bu slot bileşeni biraz zor olacak ama neler olduğunu daha sonra açıklayacağım:

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

Bu dosyadaki en önemli kısım, state.drafting. Bunu henüz bağlam içinde açıklamadık, ancak amacı bize kullanıcıya yuvaları ne zaman göstereceğimizi ve onlara nihai çıktıyı ne zaman göstereceğimizi bilmenin bir yolunu vermektir. Ne zaman state.drafting true (varsayılan değer olacak), onlara alıntılarını ve alıntının yazarını ekleyebilecekleri bloklar olan yuvaları göstereceğiz. üzerine tıkladıklarında Kaydetmek buton, state.drafting geçiş yapacak false ve bunu nihai çıktılarına bakmak istediklerini belirlemek için kullanacağız.

ilan ettik input varsayılan değeri olan parametre 'textfield' çünkü gelecekte, kullanıcıların yazmanın yanı sıra tırnak eklemelerine izin vermek için başka girdi türleri kullanmak isteyebiliriz (örnek: görüntüleri tırnak olarak yüklemelerine izin verebileceğimiz dosya girdileri, vb.). Bu eğitim için yalnızca destekleyeceğiz 'textfield'.

Öyleyse ne zaman state.drafting dır-dir true, <SlotDrafting /> tarafından kullanılır Slotve ne zaman false, <SlotStatic /> kullanıldı. Bu ayrımı ayrı bileşenlere ayırmak daha iyidir, böylece bileşenleri bir sürü ile şişirmeyiz. if/else şartlılar.

Ayrıca, alıntı/yazar giriş alanları için bazı satır içi stiller bildirmiş olsak da, yine de uyguladık className={styles.slotQuoteInput} bunu satır içi stiller ile yapamayacağımız için yer tutucuya stil verebilmemiz için. (Bu, son yenilenmiş içerik için uygundur, çünkü girdiler bile oluşturulmaz)

İşte bunun için css:

kaynak/styles.module.css

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

Geri dönelim ve ilan edelim drafting duruma göre durum:

kaynak/Sağlayıcı.js

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

Şimdi nihayet bunu içine koyalım App.js bileşen, böylece şimdiye kadar neye benzediğini görebiliriz:

(Not: Bu örnekte bir modal bileşen kullandım. anlamsal-ui-tepki hangisi olumsuzluk mod için gereklidir. Herhangi bir kip kullanabilir veya kendi düz kipinizi oluşturabilirsiniz. tepki portalı API):

kaynak/App.js

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'


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>
)

Sunucumuzu başlatmadan önce şunları bildirmemiz gerekiyor. modal durumlar (aç/kapat):

kaynak/Sağlayıcı.js

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

Ve işte şimdiye kadar sahip olmamız gerekenler:

uygulamanızı yalnızca tepki kancalarıyla oluşturun 2019

(Not: KAYDETMEK düğmesi görüntüdeki modu kapatıyor, ancak bu küçük bir hataydı. Modu kapatmamalı)

Şimdi değiştireceğiz PasteBin kullanarak yeni bir api ilan etmek için biraz React.useImperativeHandle textarea için, böylece onu kullanabiliriz useSlotify ve kancayı bir dizi işlevle şişirmiyoruz, bunun yerine kapsüllenmiş bir api sağlıyoruz:

kaynak/PasteBin.js

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 ayrıca bir olacak React.useRef hemen yanına yerleştirilecek textareaRef içinde useSlotify kanca:

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

Bu yeni api’yi slotify işlev:

kaynak/Sağlayıcı.js

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 })
}

Şimdi yapacağımız bir sonraki şey, kullanıcı yuvalara baktığında ve henüz bir yazar eklemediğini tespit ettiğimizde, dikkatlerini daha fazla çekmek için o öğeyi flaş edeceğiz.

Bunun için kullanacağımız React.useLayoutEffect içinde SlotDrafting bileşen çünkü SlotDrafting yazar girişini içerir:

kaynak/Slot.js

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>
  )
}

Muhtemelen kullanımına ihtiyacımız yoktu useLayoutEffect burada, ama sadece gösteri için. Stil güncellemeleri için iyi bir seçenek olduğu biliniyor. kanca, dom monte edildikten ve mutasyonları güncellendikten sonra çağrıldığından. Stil için iyi olmasının nedeni, çağrılmış olmasıdır. önceki sonraki tarayıcı yeniden boyama yaparken useEffect kanca daha sonra çağrılır – bu kullanıcı arayüzünde ağırbaşlı gösterişli efekt.

stiller:

kaynak/styles.module.css

.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;
  }
}

Modun altına bir koyduk SAVE çağıracak düğme onSave itibaren useSlotify. Kullanıcı buna tıkladığında, slotlar kesinleşmiş slotlara dönüşecektir (ne zaman drafting === false). Ayrıca, içeriği blog gönderilerine yapıştırabilmeleri için HTML’deki kaynak kodunu panolarına kopyalayacak yakınlarda bir düğme oluşturacağız.

Şimdiye kadar, elimizdekiler:

Her şey olacak aynı kal, artık CSS sınıf adlarıyla çalışıyoruz. Yeni css sınıfı adları için son ekleri Static ne zaman kullanıldığını belirtmek için drafting === false. Burada küçük bir değişiklik Slot CSS değişikliklerine uyum sağlamak için bileşen:

kaynak/Slot.js

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>
  )
}

Ve işte yeni eklenen CSS stilleri:

.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%);
}

Uygulamamız şu anda böyle görünüyor:

slotify2

Yapmamız gereken son şey, bir Kapat modu kapatmak için düğme ve bir kopyala sonlandırılmış blog gönderilerinin kaynak kodunu kopyalamak için düğme.

ekleme Kapat düğme kolaydır. Bu düğmeyi yanına eklemeniz yeterlidir. Kaydetmek buton. bu kopyala düğmesinin yanına yerleştirilecektir. Kapat buton. Bu düğmelere biraz verilecek onClick işleyiciler:

kaynak/App.js

<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>

Biz meli uyguladığımızda yapılacak onCopyFinalContent işlev, ama henüz değiliz. Son bir adımı kaçırıyoruz. Kesinleşmiş içeriği kopyaladığımızda, Hangi kullanıcı arayüzünün bir kısmını kopyalıyor muyuz? Modun tamamını kopyalayamayız çünkü KAYDETMEK, KAPAT ve KOPYALA blog yazılarımızdaki düğmeler yoksa çok garip görünürdü. başka bir tane yapmalıyız React.useRef ve bunu belirli bir öğeye eklemek için kullanın sadece istediğimiz içeriği içerir.

Bu yüzden biz *tamamen CSS sınıfları değil, satır içi stiller kullanıldı çünkü stillerin yenilenmiş versiyona dahil edilmesini istiyoruz.

Bildirmek modalRef içinde useSlotify:

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

Bunu yapacak öğeye takın yalnızca içeriği içerir:

kaynak/App.js

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>
  )
}

Not: Sarıldık ModalContent Birlikte React.useCallback çünkü referans istiyoruz aynı kal. Bunu yapmazsak, bileşen yeniden oluşturulacak ve tüm alıntılar/yazar değerleri sıfırlanacaktır. onSave işlevi durumu günceller. Durum güncellendiğinde, ModalContent kendini yeniden yaratacak ve yeni, taze bir boş durum yaratacak, ki bu bizim yaptığımız şeydir. yapma istek.

Ve sonunda, onCopyFinalDraft içine yerleştirilecek useSlotify kullanacak kanca modalRef referans:

kaynak/Sağlayıcı.js

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)
}

Ve işimiz bitti!

İşte şimdi uygulamamız:

web uygulamanızı yalnızca tepki kancalarıyla oluşturun 2019

Çözüm

Ve bu yazının sonu burada bitiyor! Umarım faydalı bulmuşsunuzdur ve gelecekte daha fazlasını bekleyin!

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.