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
Reselectis 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_ACTIONis 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
- Application starts, and runs all of it’s existing Sagas.
- mySaga runs, enters the ‘while (true)’ loop, and is “sleeping”.
USER_INTERACTED_WITH_UI_ACTIONaction is dispatched.- Saga’s ‘thread’ is waking up and moves to line 4, where it emits
SHOW_LOADING_ACTIONfor the reducer to handle (the reducer will now probably cause the view to show some loading indication). - 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.
SHOW_DATA_ACTIONis dispatched with the received data, so now the reducer can use it for updating the view.- 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