Expert


React: Performance

We need to minimize the number of costly DOM operations required to update the UI

  • virtualize long lists (react-virtualized)

  • avoid reconciliation (PureComponent, shouldComponentUpdate)

  • use immutable data structure

  • prefer Function/Stateless Components

  • multiple chunk files / React.lazy

  • Dependency optimization (lodash)

  • remove help functions from render

  • Throttling and Debouncing Event Action in JavaScript

    • throttling means delaying function execution

    • debouncing is a technique to prevent the event trigger from being fired too often

  • avoid index as key for map

  • avoiding Props in Initial States

  • reselect in Redux Connect to Avoid Frequent Re-render

  • React.memo for functional components

  • move some operations to webWorker


React: Computing derived Data, reselect

Reselect is a simple library for creating memoized, composable selector functions. Reselect selectors can be used to efficiently compute derived data from the Redux store.

Reselect provides a function createSelector for creating memoized selectors. createSelector takes an array of input-selectors and a transform function as its arguments. If the Redux state tree is changed in a way that causes the value of an input-selector to change, the selector will call its transform function with the values of the input-selectors as arguments and return the result. If the values of the input-selectors are the same as the previous call to the selector, it will return the previously computed value instead of calling the transform function.

  • Селекторы могут вычислять производные данные, позволяя Redux сохранять (store) минимально возможное состояние (state).
  • Селекторы эффективны. Селектор не производит вычислений, пока один из его аргументов не изменился.
  • Селекторы являются составными. Они могут использоваться в качестве входных для других селекторов.
import { createSelector } from 'reselect';

// input-selectors
const getVisibilityFilter = state => state.visibilityFilter;
const getTodos = state => state.todos;

// selector
export const getVisibleTodos = createSelector(
  [getVisibilityFilter, getTodos],
  (visibilityFilter, todos) => {
    switch (visibilityFilter) {
      case 'SHOW_ALL':
        return todos;
      case 'SHOW_COMPLETED':
        return todos.filter(t => t.completed);
      case 'SHOW_ACTIVE':
        return todos.filter(t => !t.completed);
    }
  }
);

// composing selectors (getVisibleTodos as input-selector)
const getKeyword = state => state.keyword;

const getVisibleTodosFilteredByKeyword = createSelector(
  [getVisibleTodos, getKeyword],
  (visibleTodos, keyword) =>
    visibleTodos.filter(todo => todo.text.indexOf(keyword) > -1)
);

React: Async flow in Redux

redux-saga

redux-saga is a library that aims to make application side effects (i.e. asynchronous things like data fetching and impure things like accessing the browser cache) easier to manage, more efficient to execute, easy to test, and better at handling failures.

Setup

import { createStore, applyMiddleware, compose } from "redux";
import createSagaMiddleware from "redux-saga";

const sagaMiddleware = createSagaMiddleware();

const store = createStore(
  reducer,
  compose(applyMiddleware(sagaMiddleware), reduxDevTools)
);

A common flow of React powered by Redux-Saga will start with a dispatched action. If a reducer is assigned to handle this action - the reducer updates the store with the new state and usually the view is being rendered after. If a Saga is assigned to handle the action - we usually create a side-effect (like a request to the server), and once it’s finished, the Saga dispatches another action for the reducer to handle.

The mental model is that a saga is like a separate thread in your application that's solely responsible for side effects. redux-saga is a redux middleware, which means this thread can be started, paused and cancelled from the main application with normal redux actions, it has access to the full redux application state and it can dispatch redux actions as well.

It uses an ES6 feature called Generators to make those asynchronous flows easy to read, write and test.

Effects

  • take

    blocking the execution of the Saga until someone dispatches the action given as a parameter. Once USER_INTERACTED_WITH_UI_ACTION is dispatched, the method execution will end, just like we saw earlier with the generators (done = true).

  • takeLatest

    is a powerful Saga effect, which cancels previous Saga executions per action, once a new action is dispatched

  • put

    effect which dispatches an action (something like dispatch)

  • call

    which takes a function and an argument, and executes the function using those arguments

  • fork

    Using fork and spawn, we can execute a new ‘Saga thread’, which is actually executing a new Saga from an existing Saga’s context. This is convenient when we have a complex task, built from smaller tasks which can run independently from one another.

  • select

    Creates an effect that instructs the middleware to invoke the provided selector on the current Store's state (i.e. returns the result of selector(getState(), ...args)).

    selector: Function - a function (state, ...args) => args. It takes the current state and optionally some arguments and returns a slice of the current Store's state

    args: Array<any> - optional arguments to be passed to the selector in addition of getState.

    If select is called without argument (i.e. yield select()) then the effect is resolved with the entire state (the same result of a getState() call).

    import { take, fork, select } from 'redux-saga/effects';
    import { getCart } from './selectors';
    
    function* checkout() {
      // query the state using the exported selector
      const cart = yield select(getCart);
    
      // ... call some API endpoint then dispatch a success/error action
    }
    
  • cancel

    Creates an Effect description that instructs the middleware to cancel a previously forked task.

    import { CANCEL } from 'redux-saga'
    import { fork, cancel } from 'redux-saga/effects'
    
    function myApi() {
      const promise = myXhr(...)
    
      promise[CANCEL] = () => myXhr.abort()
      return promise
    }
    
    function* mySaga() {
    
      const task = yield fork(myApi)
    
      // ... later
      // will call promise[CANCEL] on the result of myApi
      yield cancel(task)
    }
    
// saga.js
import { take, put, call } from 'redux-saga/effects';

function* mySaga(){ 
  while (true) {
    yield take(USER_INTERACTED_WITH_UI_ACTION);
    yield put(SHOW_LOADING_ACTION, {isLoading: true});
    const data = yield call(GET, 'https://my.server.com/getdata');
    yield put(SHOW_DATA_ACTION, {data: data});
  }
}
// reducer.js
// ...
case SHOW_LOADING_ACTION: (state, isLoading) => {
    return Object.assign({}, state, {showLoading: isLoading});
},
case SHOW_DATA_ACTION: (state, data) => {
    return Object.assign({}, state, {data: data, showLoading: false};
}

Step by step

  1. Application starts, and runs all of it’s existing Sagas.
  2. mySaga runs, enters the ‘while (true)’ loop, and is “sleeping”.
  3. USER_INTERACTED_WITH_UI_ACTION action is dispatched.
  4. Saga’s ‘thread’ is waking up and moves to line 4, where it emits SHOW_LOADING_ACTION for the reducer to handle (the reducer will now probably cause the view to show some loading indication).
  5. We send a request to the server (line 5), and “sleep” until the promise is resolved with content that is stored in the ‘data’ variable.
  6. SHOW_DATA_ACTION is dispatched with the received data, so now the reducer can use it for updating the view.
  7. We enter the loop again, and go back to the second step.

React: Testing

Jest

  • Matchers: expect(value)

    • .toBe()
    • .toEqual()
    • .toBeNull()
    • .toContain()
    • .not.toBe()
  • Snapshot

  • Mocks:

    • mock function: jest.jn(),

    • jest.mockImplementation(), jest.mockImplementationOnce(),

    • jest.mockReturnValue(), jest.mockReturnValueOnce()

    • mock module: jest.mock('axios'); jest.mock('../SomeDirectory/SomeComponent', () => 'SomeComponent');

    • jest.useFakeTimers(), jest.runAllTimers(), jest.advanceTimersByTime(1000), jest.clearAllTimers()

  • describe, it, beforeAll, beforeEach, afterAll, afterEach

    describe('Scoped / Nested block', () => {
      beforeAll(() => {});
      afterAll(() => {});
      beforeEach(() => {});
      afterEach(() => {});
      it('', () => {});
    });
    

Example:

test('CheckboxWithLabel changes the text after click', () => {
  const wrapper = shallow(<CheckboxWithLabel labelOn="On" labelOff="Off" />);

  expect(wrapper.text()).toEqual('Off');
  wrapper.find('input').simulate('change');
  wrapper(checkbox.text()).toEqual('On');
});

Enzyme (airBnB)

--save-dev enzyme enzyme-adapter-react-16

import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

Enzyme.configure({ adapter: new Adapter() });
  • shallow
  • mount (full dom rendering)
  • render (static renderer markup)

test Redux

  • redux-mock-store

React: React Fiber

Reconciliation

Reconciliation - алгоритм, используется чтобы отличить одно дерево элементов от другого, чтобы определить части, которые можно заменить

  • если у компонентов разные типы - React не будет их дополнительно сравнивать, а заменит старое дерево полностью
  • разницу в списках находит с помощью уникальных ключей ```keys``

Update

Update - именение в данных, которые используются для отрисовки React приложения (изменение props, state, sinteticEvents)

Так как React может рендерится не только в DOM (React Native использует iOS/Android Views), reconciliation и rendering - отдельные процессы.

Fiber - переделанная реализация алгоритма reconciliation

  • scheduling - процесс планирования, определяет, когда работа должна быть выполнена

  • work - любые вычисления, которые должны быть выполнены. Обычно work это результат апдейта (например, вызов setState)

Fiber позволяет

  • остановить work и вернуться к ней позже
  • расставлять приоритеты разным типам работы
  • переиспользовать работу, выполненную раньше
  • отменить работу, если она больше не нужна

Fiber

  • requaestAnimationFrame, requestIdleCallback

  • приоритеты (6 уровней)

    • , взаимодействие с input, scrollling, animation
    // fiber sourcew code (priority)
    module.exports = {
      NoWork: 0,
      SynchronousPriority: 1,   // пользовательский ввод с клавиатуры
      TaskPriority: 2,
      AnimationPriority: 3,     // пользовательские анимации
      HighPriority: 4,          // скроллинг
      LowPriority: 5,           // обновление данных пришедших с сервера
      OffscreenPriority: 6,     // компоненты вне экрана
    }
    

Fiber(волокно) - это легковесная реализация потоков, подстроенная под React компоненты

он уже используется в React 16