استفاده از context api یا Redux (به دور از تعصب)

کسایی که منو میشناسن ، میدونن که روی context api خیلی متعصبم ( تعصب بی جا ) . دلیلش هم داشتن یک سری استاندارد ها و یک سری امکانات خوبه که کار رو راحت میکنه .

تقریبا میشه گفت دو ساله با ریداکس آشنا هستم . ( چون سخته بگی به چی تسلط داری یا نداری . نمیتونم بگم تسلط دارم . اونایی که میگن به فلان چیز تسلط صد در صد دارن خیلی شجاعن ) .
با حرف عامر لطفی خیلی موافقم ( برنامه نویس شیپور ) ، همه اینا فقط ابزار هستن و باید بدونیم از کدوم کجا استفاده کنیم .
در نهایت واقعا همینه .
اما منم یک سری تعصبات بی جا روی سیستم عامل ، ابزار ، لایبرری و... دارم که دوست دارم در موردشون بنویسم و بخونم .

ریداکس چیه؟

A predictable state container for JavaScript apps.

یک قالب برای بررسی تغییرات state ها داخل اپلیکیشن های جاوا اسکریپتی . ( موبایل ، وب و ... ) که خیلی منعطف و قوی هست و Dan abramov نویسنده این لایبرری بود . کسایی که Dan Abramov رو میشناسن ، حرفش رو حرف حق میدونن و سند . یعنی میگن دیگه هرچی ایشون بگه همونه . اما به دید من اینطور نیست . خیلی قوی تر از ایشون هم هست که به علت شناخته نشدنشون ، حرفشون خونده نمیشه .
داخل ایران هم هستن افرادی که واقعا کارشون درسته و از ایشون قوی ترن . باید ببینیم هرکس در مورد چیزی که میگه ، چه دیدگاهی داره . شاید Dan Abramov به علت سخنرانی هایی که در مورد ریداکس میکنه ، مایل نیست که بگه context جایگزینی برای redux هست . ( یا نیست ) . پس باید خودمون تحقیق کنیم .

ریداکس اوایل خیلی سنگین بود و کار باهاش واقعا اذیت میکرد . اما به مرور هم کار باهاش توجیه شد هم راه های نوشتنش آسونتر .
اما واقعا راحتی کار رو نمیشه با context قیاس کرد .

نمیخوام بگم چه شکلی کد بنویسیم . فقط دو مثال میزنم و ادامه بحث

نمونه کد :

Action.js

// Action Types
export const COUNTER_INCREMENT = 'COUNTER_INCREMENT'
export const COUNTER_DECREMENT = 'COUNTER_DECREMENT'

// Action Creators
export function incrementCounter () {
  return {
    type: COUNTER_INCREMENT
  }
}

export function decrementCounter () {
  return {
    type: COUNTER_DECREMENT
  }
}

Reducer.js

import {
  COUNTER_INCREMENT,
  COUNTER_DECREMENT
} from './actions'

const counterInitialState = {
  count: 0
}

function counterReducer (state = counterInitialState, action) {
  switch (action.type) {
    case COUNTER_INCREMENT:
      return Object.assign({}, state, { count: ++state.count })
    case COUNTER_DECREMENT:
      return Object.assign({}, state, { count: --state.count })
    default:
      return state
  }
}
export default counterReducer

store.js

import { createStore } from 'redux'
import reducers from './reducers'

export default createStore(reducers)

app.js

import { Provider } from 'react-redux'
import store from './store'
import Counter from './counter'

class App extends Component {
  render() {
    return (
      <Provider store={store}>
        <div className="app">
          Redux Example
                   <Counter />
        </div>
      </Provider>
    )
  }
}

counter.js

import React, { Component } from 'react'
import { connect } from 'react-redux'
import {
  incrementCounter,
  decrementCounter
} from './actions'

const mapDispatchToProps = dispatch => ({
  increment: () => dispatch(incrementCounter()),
  decrement: () => dispatch(decrementCounter())
})

const mapStateToProps = state => ({
  counter: state.count
})

class Counter extends Component {
  render () {
    return (
      <div className="counter">
        Counter: <span>{this.props.counter}</span>
                  <div>
                      <button ={ this.props.decrement }>-</button>
                      <button ={ this.props.increment }>+</button>
                   </div>
      </div>
    )
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Counter)

الان که پروژه ران بشه ، یک counter داریم که شمارش از 1 شروع میشه به بالا .

حالا میخوام همین پروژه رو تغییر بدم به context api . اتفاقاتی که میفته کمی جالب میشه . همین کامپوننت ها رو تغییر میدم و پروژه رو به context تبدیل میکنم .

مثال دوم :

ایجاد فایل context.js
import { createContext } from 'react'

export const { Provider, Consumer } = createContext({
  count: 0,
  increment: () => {},
  decrement: () => {}
})

app.js

import React, { Component } from 'react'
import Counter from './counter'
import { Provider as CounterProvider } from './context.js'

import './app.css'

class App extends Component {
  state = {
    count: 0,
    increment: () => { this.setState({count: this.state.count + 1}) },
    decrement: () => { this.setState({count: this.state.count - 1}) }
  }

  render() {
    return (
      <CounterProvider value={this.state}>
        <div className="app">
          Redux Example
          <Counter />
        </div>
      </CounterProvider>
    )
  }
}

export default App

counter.js

import React, { Component } from 'react'
import { Consumer as CounterConsumer } from './context'

class Counter extends Component {
  render () {
    return (
      <CounterConsumer>
        {
          ({count, increment, decrement}) => (
            <div className="counter">
              Counter: <span>{ count }</span>
              <div>
                <button ={ decrement }>-</button>
                <button ={ increment }>+</button>
              </div>
            </div>
          )
        }
      </CounterConsumer>
    )
  }
}

export default Counter

خوب ، تمام .

تغییراتی که اتفاق افتاد حذف reducer , actions و store بود .

حذف تابع connect هم به عنوان یک HOC که هر بار در رندر شدن کامپوننت ، تاثیر میزاره .

البته این از دید سادگی context نسبت به redux بود .

دیدگاه فنی :

دیدگاهی که مینویسم ، دیدگاه مین نیست. بلکه یک دیدگاه کلی نسبت به دو سیستم هست .

پرفورمنس context نسبت به ریداکس به علت استفاده از memoized تا حد قابل توجهی بهتر . ریداکس با استفاده از mapStateToProps یک روند رو داخل connect تکرار میکنه . یعنی اگر کامپوننت ۱۰۰ مربته کال بشه ، روند map ۱۰۰ مربته انجام میشه و در نهایت به جایگاه قبل برمیگرده .

این جواب رو داخل لینک زیر دیدم که خالی از لطف نیست :

https://www.reddit.com/r/reactjs/comments/a5sddz/redux_vs_context_api_performance/

موارد بعدی که میشه بین این دو سیستم مقایسه کرد ،استفاده در حجم اپلیکیشن های بالاست . paypal خیلی از استفاده ریداکس راضی هست اما ریسک انتقال رو به کانتکست انجام نداد .
در واقع هنوز سایت پر مخاطبی با context نوشته نشده . ( یا هوز بروز ندادن ) . به همین دلیل نمیشه روی سایت های بزرگ این ادعا رو داشت که بهتره یا نه . در واقع پرفورمنس با مقیاس کوچک ، خیلی بهتره ، اما این امکان هم هست که در مقیاس بالا این حرف درست نباشه .

امکاناتی مثل redux thunk باعث شده که ریداکس محبوبیت هایی داشته باشه که داخل context دیده نمیشه . این امکانات جانبی ریداکس رو از کانتکست برتر کرده .

البته هنوز منتظر تغییرات اساسی context برای ورژن 16.8 به بالا هستیم . تا الان لیست تغییراتی که اعلام شده ، در مورد context api چیزی نگفته .


صحبت های Dan Abramov

حالا چرا من با حرف های ایشون مخالفم؟

داخل توییتی که انجام دادن ، گفتن که:

Most apps that use Redux today probably don’t need it. But I’m glad to hear success stories about cases where Redux-like architecture ends up being actually useful.

اما داخل همون توییت در جواب نوشتن :

Context is what powers React Redux under the hood. The new context API isn’t to “replace Redux”, it just fixes a broken feature that needs to be provided by React. Whether you use it directly or through another API like RR is up to you
https://twitter.com/dan_abramov/status/964923915448586240

به همین دلیل نمیتونم استناد کنم به حرف هایی که هر لحظه در حال تغییرن .