[JavaScript Weekly #570] Compare Svelte and React

Posted by jspodisus on Sun, 09 Jan 2022 16:13:32 +0100

🥳 Welcome interested partners to do something meaningful together!

I launched a weekly translation program, Warehouse addressAccess address

There is still a shortage of like-minded partners. They are purely personal interests. Of course, they will also help to improve English and front-end skills. Requirements: English is not too bad, github is proficient, persistent, modest and responsible for what you do.

If you want to participate, you can wx You can also send issue messages to the warehouse. My blog also has specific personal contact information: daodaolee.cn

preface

Pomodone: a small time tracking application based on tomato working method working at 25 minute intervals. It has a 25 minute timer (running in Web Worker) and saves the history of "poms" to a small Firebase database, Portal

Usage of both

The hook of React is a good encapsulation. I created a useCurrentUser hook to listen for authentication changes and set some states accordingly. Then, when I notice a change in authentication, I can be sure that React will re render as needed.

export const useCurrentUser = () => {
  const [currentUser, setCurrentUser] = useState(undefined)

  useEffect(() => {
    return firebase.auth().onAuthStateChanged((details) => {
      setCurrentUser(
        details
          ? {
              displayName: details.displayName,
              provider: {
                'google.com': 'Google',
                'github.com': 'GitHub',
              }[details.providerData[0].providerId],
              uid: details.uid,
            }
          : null
      )
    })
  }, [])
  return [currentUser]
}

In any component, it can be written as follows:

const [currentUser] = useCurrentUser()

This way of writing, the workload is very small, and any component can quickly access the current user. The only disadvantage is that there may be many onAuthStateChanged listeners, which can be avoided by binding only one listener or putting the current user in the current context.

When it comes to context, it is closer to the writing method in Svelte: Writable Store

export const currentUser = writable()

export const listenForAuthChanges = () => {
  return firebase.auth().onAuthStateChanged((details) => {
    if (details) {
      currentUser.set({
        displayName: details.displayName,
        provider: {
          'google.com': 'Google',
          'github.com': 'GitHub',
        }[details.providerData[0].providerId],
        uid: details.uid,
      })
    } else {
      currentUser.set(null)
    }
  })
}

In advanced Svelte components, you can call it in onMount, which will run once the component is installed (the method will be returned, so you can cancel the subscription when you remove the component, just like useEffect returns a function).

onMount(() => {
  return listenForAuthChanges()
})

Now, the component can import the currentUser writable store anywhere in the Svelte code. What I care about is that currentUser is not a value, but a store, so it has absolute state control. Of course, you can subscribe to it and manually modify the status changes:

currentUser.subscribe(newValue => {
  ...
})

Alternatively, if you only want to read the latest value, you can add a $prefix:

console.log($currentUser)

The above are some syntax skills of Svelte. The $symbol will automatically subscribe to the latest value in the store. Its advantage is that Svelte will not have to subscribe every time it needs to read the latest value.

In short, both libraries seem to adopt a similar approach: both allow subscription to a Firebase listener (Demo above) and update data when the state changes.

Using Worker

I designed) must keep the 25 minute timer running as accurately as possible. If browser tabs are in the background (for example, not the focus tab), most browsers will lower the priority of their setTimeout calls instead of running them strictly on time. Most of the time, this is not a big problem on the network, but when users track 25 minutes of work through your application, the problem occurs! In addition, during the 25 minute process, any small time difference will lead to a long distance between the final times. However, if these timeouts are moved to the Web Worker, they will run on time and will not be de prioritized by the browser.

Therefore, in my Tracker component, I need to instantiate a web worker to send information and receive data (such as the remaining time). I found that React is more cumbersome than Svelte management. Because the React component will be re executed every time the component is re rendered, it is easy to create thousands of workers. At this time, you need to use useRef to reference the created workers to avoid this problem.

First, I set the required initial state of the component:

const [currentPom, setCurrentPom] = useState(null)
const [currentUser] = useCurrentUser()
const worker = useRef(null)

Then, create a useEffect hook to instantiate the worker, and bind an event if necessary:

useEffect(() => {
  if (!worker.current) {
    worker.current = new Worker(workerURL)
    window.worker = worker.current
  }

  const onMessage = (event) => {
    if (event.data.name === 'tick') {
      setCurrentPom((currentPom) => ({
        ...currentPom,
        secondsRemaining: event.data.counter,
      }))
    } else if (event.data.name === 'start') {
      // More branches removed here to save space...
    }
  }
  worker.current.addEventListener('message', onMessage)

  return () => {
    worker.current.removeEventListener('message', onMessage)
  }
}, [currentUser])

Next, when the user clicks the Start button, a message will be sent to the worker:

const onStartPom = () => {
  if (!worker.current) return
  worker.current.postMessage('startTimer')
}

If written in Svelte, the code is very similar, but there are two places that will be easier:

  1. You don't have to keep the worker on userRef, just allocate a variable
  2. The event listener can be encapsulated in a function, and the function will not become a dependency of userEffect - that is, encapsulated in userCallback.
let worker
onMount(() => {
  worker = new Worker(workerURL)
  worker.addEventListener('message', onWorkerMessage)
  return () => {
    worker.removeEventListener('message', onWorkerMessage)
  }
})

There is no need to use the setx (oldx = > newx) constraint of React to set the state, just change the local variables:

function onWorkerMessage(event) {
  if (event.data.name === 'tick') {
    currentPom = {
      ...currentPom,
      secondsRemaining: event.data.counter,
    }
  } else if (event.data.name === 'start') {
    // More branches here removed to save space...
  }
}

conditional rendering

Part of the reason I like React is that JS is embedded in the template syntax:

// React
<ul>
  {pomsForCurrentDay.map(entryData, index) => {
    const finishedAt = format(new Date(entryData.timeFinished), 'H:mm:ss')
    return <li title={`Finished at ${finishedAt}`}>{index + 1}</li>
  })}
</ul>

But Svelte's template syntax also works well:

// Svelte
<ul class="poms-list">
  {#each currentDayPoms as value, index}
    <li
      title={`Finished at ${format(
        new Date(value.timeFinished),
        'H:mm:ss'
      )}`}
    >
      {index + 1}
    </li>
  {/each}
</ul>

When rendering components, Svelte's syntax is also very simple, much like JSX:

// Svelte
<History pomodoros={pomodoros} />
  
// Svelte(collapsed props)
<History {pomodoros}/>

Response in Svelte$

React requires that you use userEffect and other hooks to control the code running and rerun the code when you re render the component. Svelte differs in that, by default, most of the code runs only once. Of course, when the node rendering function with a large amount of data runs, the re rendering of react also has its advantages. For example:

const input = props.inputData
const transformed = input.map((item) => transformItem(item))

return <div>{JSON.stringify(transformed, null, 2)}</div>

If the user provides a new props Inputdata, the component will re render and update, and the obtained value is also new. This is not the case in Svelte:

<script>
export let input;
const transformed = input.map((item) => transformItem(item))
</script>

<div>{JSON.stringify(transformed, null, 2)}</div>

To solve the reactive problem, either use the $symbol or add the transformation logic to the template:

// Use$
<script>
export let input;
$: transformed = input.map((item) => transformItem(item))
</script>

<div>{JSON.stringify(transformed, null, 2)}</div>

// Use template
<script>
export let input;
</script>

<div>{JSON.stringify(input.map((item => transformItem(item))), null, 2)}</div>

Combined assembly

Composite components are particularly important in the component level framework. The children attribute of React simplifies rendering:

function Box(props) {
  return <div>{props.children}</div>
}

function App() {
  return (
    <Box>
      <p>hello world!</p>
    </Box>
  )
}

Slots used in Svelte:

<!-- Box component -->
<div class="box">
  <slot></slot>
</div>

<!-- App component -->
<Box>
  <p>hello world!</p>
</Box>

When multiple sub components are involved, they will be handled in different ways:

  • React will have multiple Props:

    function Box(props) {
      return (
        <div>
          <div class="left">{props.left}</div>
          <div class="right">{props.right}</div>
        </div>
      )
    }
    
    function App() {
      return <Box left={<p>hello</p>} right={<p>world!</P>} />
    }
  • Multiple named slots are displayed:

    <!-- Box component -->
    <div class="box">
      <slot name="left"></slot>
      <slot name="right"></slot>
    </div>
    
    <!-- App component -->
    <Box>
      <p slot="left">hello</p>
      <p slot="right">world!</p>
    </Box>

Class name judgment

Svelte has a small function to assign class conditionally:

<div class:is-active={isActive}>

event listeners

Svelte contains some modifiers for binding events:

<script>
function click() {
  // No need to preventDefault ourselves
  // logic here
}
</script>

<button on:click|preventDefault={click}>
  Click me!
</button>

Related links

Original link

Original translation plan

Topics: Javascript Front-end React svelte