Skip to content

Instantly share code, notes, and snippets.

@rabelais88
Created November 10, 2019 05:21
Show Gist options
  • Save rabelais88/19bfe8dfd29d901554389f0a8cc8947a to your computer and use it in GitHub Desktop.
Save rabelais88/19bfe8dfd29d901554389f0a8cc8947a to your computer and use it in GitHub Desktop.

Svelte.js

왜 svelte가 Vue 보다 더 좋은가? By John Hannah

  • 내(박성렬)가 Svelte에 관심을 가지게 된 이유.
  • 글이 주장하는 바는 대략 세 가지
    • 구문이 간결하다.
    • Vue보다 빠르다(bootup time, main thread cost)
    • 용량이 Vue보다 적다(total byte weight)

store comparison

// ---- Vue Store
// 1. initialization
const initState = () => ({
  count: 0
})
const MUTATION = {
  ADD: 'ADD'
}
const counter = {
  namespaced: true,
  state: initState(),
  mutations: { // 1-1. mutation
    [MUTATION.ADD](state) {
      state.count += 1;
    }
  },
  getters: {
    isBig(state) {
      return state >= 10;
    }
  },
  actions: {
    async add({ commit }) => { // action is now async
      commit(MUTATION.ADD)
    }
  }
})
// 2. store injection + namespace
import 'Vuex';
Vue.use(Vuex);
new Vuex.store({
  modules: {
    counter
  }
})
// 3. subscription
import mapState from 'mapState';
const component = {
  ...mapState({
    count: 'counter/count'
  })
}
// 4. dispatch
Vue.$store.dispatch('counter/add');
// 5. in-component dispatch
import mapActions from 'mapActions';

const component = {
  ...mapActions({
    add: 'counter/add' 
  })
}

// ----- React Store
// 1. initialization
const ACTION = {
  ADD: 'ADD'
}
// 1.1 - reducer
const initState = () => ({
  count: 0
})
const initializedState = initState();
const counterReducer = (state = initializedState, action) => {
  switch(action.type) {
    case ACTION.ADD:
      return {...state, count: state.count += 1}
    default:
      return state;
  }
}


// 1.2 - mutation
const addAction = () => ({
  type: ACTION.ADD;
})
// 1.3 - action(redux-thunk)
export const add = () => async (dispatch, getState) => {
  dispatch(ACTION.ADD())
}
// 2. store injection + namespace(combine reducer)
import { combineReducers } from 'redux';
import { Provider } from 'react-redux';
import { CreateStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk' // provides async nature to actions

const reducers = combineReducers({
  counter: counterReducer
})

const initialStates = {
  counter: initializedState
}

const store = createStore(reducers, intialStates, applyMiddleware(thunk));
ReactDOM.render(
  <Provider store={store}>
    <App />   
  </Provider>
)
// 3. subscription and dispatch
import { connect } from 'react-redux';
import { addAction } from './store/counter'
const mapStateToProps = state => ({ // 3-1. subscription
  counter: state.counter
})
const mapDispatchToProps = dispatch => ({ // 3-2. dispatch
  add: arg => dispatch(addAction(arg))
})
const connectedApp = connect(mapStateToProps, mapDispatchToProps, App);

// ----- svelte store
// simpler in many ways, but doesn't guarantee an easy use.
// 1. initialization
import { writable } from 'svelte/store';
const initState = () => ({
  count: 0,
})
const counter = writable(initState());
const add = async () => counter.update(state => ({ // action is now async
  ...state,
  count: state.count += 1;
}))
// no namespace initialization
import counter, { initState } from './store/counter';

// 2. store injection + namespace
// ...in-script
let storeState = {
  count: initState();
}
counter.subscribe(counterState => ({ // 3. subscription
  storeState = {...storeState, count: counterState} // this scope should always be placed inside component. otherwise, it'll crash. -> test not available
}))
// 4. action(both in-component or external)
add();

component comparison

// Vue
const Component = {
  data: () => ({ // 1. state declaration
    count: 0
  }),
  methods: {
    add(num) { // 2. method
      this.count += num;
    },
    watch: { // 3. reactive method
      add(newNum) {
        console.log('num changed')
      }
    }
  },
  template: `
<div>
  <p>{{count}}</p>
  <p v-if="count > 10">number is big</p>
  <button @click="add(1)">add</button>
</div>
`
}
// React - latest functional style
import { useState, useEffect } from 'react'
const Component = props => {
  const [count, setCount] = useState(0) // 1.state declaration
  const add = num => { // 2. method
    setCount(count + 1);
  }
  useEffect(() => { // 3. reactive method
    console.log('num changed')
  }, [num])
  return <div>
    <p>{count}</p>
    {count > 10 && <p>number is big</p>}
    <button onClick={() => add(1)}>add</button>
  </div>
}
// svelte
// *actually a VM
// prop bind is simple & automatic. intentionally omitted from this example.
<script>
  let count = 0; // 1. state declaration
  const add = () => { // 2. method
    count += 1;
  }
  $: { // 3. reactive method - this is wrongly interpreted in javascript interpreters
    count // same as Vue.computed(). there's no equivalent of Vue.watch()
    console.log('num changed') // no way to explicitly declare it's a change about count. - https://github.com/sveltejs/svelte/issues/2727
  }
</script>
<div>
  <p>{count}</p>
  {#if count > 10} // this interprets wrongly in jsx interpreters
    <p>number is big</p>
  {/if}
  <button on:click={add}>add</button>
</div>

실 사용시 장점과 단점

  • 장점
    • .svelte 파일의 구조가 좀더 간결하고 좀더 명확(explicit)하다고 느껴지는 부분이 있음.
    • Vue 보다도 극도로 간결한 일부 구문들. computed$:
    • 현 시점에서 React 혹은 Vue 보다도 극단적으로 더욱 this. 를 볼일이 없음.
    • 외관상으로는 Vue 처럼 간결하면서, 스크립트는 React 처럼 작성이 가능하다.
    • store의 사용이 매우 간결함.
  • 단점
    • 기능의 블랙박스화: 내부 기능에 접근하거나 디버깅하기가 어려움. 표현은 explicit하지만, 구현은 implicit함. Vue도 opinionate된 구문을 사용하고 있어서 React전용 라이브러리가 아닌 라이브러리를 사용할 때 어려움이 있음. Svelte역시 똑같은 문제점을 가지고 갈 것으로 예상됨.
    • nested store가 존재하지 않음. 이를 hook과 유사한 형태의 초기화 방식을 가진 단일 store로 해결하려 하는데, 개발자 입장에서는 더 불편함.
    • 일부 reactivity 구문 & subscription 구문에 있어서 값의 변화를 인지하는 scope가 불명확함. 단순 기능 구현자체는 문제가 아닌데, 사용시 혼동이 있어 높은 확률로 인재형 버그를 발생시킬 것임.
    • 아직 해결되지 않은 버그들이 일부 존재함. bind:this 등...
    • 또한 Svelte가 쓸 데 없는 라이브러리를 포함시키고 있다. 실제로도 예를 들어 React나 Vue에서는 애니메이션을 별도의 라이브러리로 처리하고 있는데 Svelte는 이상하게도 d3가 아닌데도 d3용 easing 라이브러리를 기본 제공하고 있다. d3용 easing 라이브러리는 한계가 명확해서 애니메이션 전용의 라이브러리를 제공하는것이 더 낫다. react-spring이 아주 좋은 예.
    • 제일 위의 John hannah의 글에서 주장하는 퍼포먼스 테이블 역시 Svelte에 제일 유리한 부분만 포함시키고 있으며, 실제 내용을 살펴보면 전체적인 퍼포먼스는 여전히 Vue가 제일 빠르다.
    • Vue, react와 비교했을 때 값의 변화에 대한 결과값 연산은 가능하지만, 명시적으로 특정 값에 대한 추적은 불가능하다.

vue vs react vs svelte by Evan You, Vue 개발자

  • Svelte의 가장 치명적인 단점들:
    • cross-component function을 작성할 수 없음. 모든 구문이 같은 reactive scope를 공유해야하기 때문에 펑션 내부가 항상 컴포넌트 내부에 노출되어 있어야 함. 따라서 분리하여 import/export 할 수 없다. 이런 경우에는 오히려 this. 가 요긴할 수 있는데 이것마저 버려버림...
    • $: 구문을 사용하고 있어서 typescript 같은 별도의 타입 시스템이나 다른 vanilla javascript 기반 해석기구와 동시 사용이 어려움. 린팅이라던지...

왜 리액트는 Svelte보다 파일 용량이 크며 복잡한 구문을 사용하는가? by React Team

  • 매우 흥미로운 글이므로 프론트엔드 담당은 꼭 읽어볼 필요가 있음
  • 여기서 Svelte를 아주 대차게 까고 있는데, 실사용 프로젝트에서는 파일 용량이 오히려 React보다 커진다고 한다. 왜냐하면 Svelte는 겉으로 보기엔 plain HTML처럼 생겼지만, 이 글에 따르면 실제로는 Vue처럼 컴포넌트마다 VM instance 를 생성하기 때문이다!! 실제 소스코드에서 DOM을 렌더하는 부분 초기 홍보 내용에 HTML마크업과 가깝기 때문에 퍼포먼스가 뛰어나다고 한 적이 있는데 이러면 대체 뭐하러 HTML에 가까운 형태를 자랑한건지 모르겠음.
  • react에 짧은 closure(() => {})가 반복적으로 사용되기 때문에 복잡해 보이지만, 실제로는 값 중복 업데이트시에 이후의 업데이트가 무시되는 일을 막을 수 있고, scope가 분명해지기 때문에 오히려 더 정확하다.

nested store에 대한 공식 svelte github issue

svelte store spec on svelte v3

결론

구문으로 보았을 때에는 제일 간결하고 이해하기 쉬움. 이 점때문에 개발자 커뮤니티에서 많은 찬양을 받는 것으로 보임. 그러나 마크업과 일부 구문에서 지나치게 비표준적인 모습을 보이고 있음. 특히 ejs를 닮은 {#if} 블럭은 좀 시대착오적임...요놈 때문에 pug나 다른 html플러그인을 사용할 수 없음. 좀더 좋은 대안은 없었을까... Svelte는 여러 면에서 당장의 편리함을 위해 많은 부분에서 깊은 고려를 하지 않은 티가 남. Vue가 React의 실수를 설계부터 잡았다면, React는 이를 나중에서야 Hook이라는 것으로 보완했음. Svelte는 벌써 3버전인데 Scope에서 React와 비슷한 종류의 실수를 하고 있음. 이처럼 책임감 없는 개발 때문에 Backward compatibility에 있어서 Vue나 React보다 극단적으로 상황이 안좋음. 아직 사용자 베이스가 적어서 큰 소란이 없지만 개발진의 실력에 의문이 많이 듬. 좀더 완성형에 가까운 Vue를 버리고 굳이 Svelte로 갈아탈 이유는 없어 보임. React나 Vue나 모두 후반 버전으로 갈수록 처음과 달리 느린 퍼포먼스를 보여주고 있는데, 이는 보안 관련 이슈나 버그에 대처하면서 필요한 기능을 추가하다 보면 프레임웍으로써는 피할 수 없는 흐름임. 또한 Svelte 자체가 No DOM이라는 약속을 어기면서, 결국 더 빨라질거라는 기대하기가 어렵게 됐음.

@Neptunium1129
Copy link

현재 상황은 어떨까요? 비슷한거같기도..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment