1 of 120

Dependency injection

в React

Мостовой Никита,

@xnimorz

1

2 of 120

2

3 of 120

3

4 of 120

4

Команда архитектуры фронтенда

  • Качество кода

Мостовой Никита

twitter: @xnimorz

https://xnim.ru

5 of 120

5

Команда архитектуры фронтенда

  • Качество кода
  • Культура разработки

Мостовой Никита

twitter: @xnimorz

https://xnim.ru

6 of 120

6

  • Качество кода
  • Культура разработки
  • Инфраструктура

Команда архитектуры фронтенда

Мостовой Никита

twitter: @xnimorz

https://xnim.ru

7 of 120

7

  • Качество кода
  • Культура разработки
  • Инфраструктура
  • Архитектура�

Команда архитектуры фронтенда

Мостовой Никита

twitter: @xnimorz

https://xnim.ru

8 of 120

8

  • Качество кода
  • Культура разработки
  • Инфраструктура
  • Архитектура
  • Производительность�

Команда архитектуры фронтенда

Мостовой Никита

twitter: @xnimorz

https://xnim.ru

9 of 120

9

10 of 120

10

11 of 120

11

12 of 120

12

13 of 120

13

14 of 120

14

15 of 120

15

16 of 120

16

17 of 120

17

18 of 120

18

19 of 120

19

20 of 120

20

21 of 120

function SearchPage({ query, searchResults }) {

return (

<Layout>

<Row>

<Column>

<SearchForm query={query.text} />

</Column>

</Row>

<Row>

<Column>

<SearchTabs active={query.kind} />

</Column>

</Row>

21

22 of 120

function SearchPage({ query, searchResults }) {

return (

<Layout>

<Row>

<Column>

<SearchForm query={query.text} />

</Column>

</Row>

<Row>

<Column>

<SearchTabs active={query.kind} />

</Column>

</Row>

22

23 of 120

function SearchPage({ query, searchResults }) {

return (

<Layout>

<Row>

<Column>

<SearchForm query={query.text} />

</Column>

</Row>

<Row>

<Column>

<SearchTabs active={query.kind} />

</Column>

</Row>

23

24 of 120

function SearchPage({ query, searchResults }) {

return (

<Layout>

<Row>

<Column>

<SearchForm query={query.text} />

</Column>

</Row>

<Row>

<Column>

<SearchTabs active={query.kind} />

</Column>

</Row>

24

25 of 120

<Row>

<Column>

<Title>Заголовок</Title>

</Column>

</Row>

...

<SearchResults query={query} searchResults={searchResults}/>

</Layout>

);

}

25

26 of 120

<Row>

<Column>

<Title>Заголовок</Title>

</Column>

</Row>

...

<SearchResults query={query} searchResults={searchResults}/>

</Layout>

);

}

26

27 of 120

function SearchResults({ query, searchResults }) {

return (

<Row>

<Column>

<Clusters query={query} />

</Column>

<Column>

<SearchSettings query={query} />

<Vacancies searchResults={searchResults} />

</Column>

</Row>

);

}

27

28 of 120

function SearchResults({ query, searchResults }) {

return (

<Row>

<Column>

<Clusters query={query} />

</Column>

<Column>

<SearchSettings query={query} />

<Vacancies searchResults={searchResults} />

</Column>

</Row>

);

}

28

29 of 120

function SearchResults({ query, searchResults }) {

return (

<Row>

<Column>

<Clusters query={query} />

</Column>

<Column>

<SearchSettings query={query} />

<Vacancies searchResults={searchResults} />

</Column>

</Row>

);

}

29

30 of 120

30

Page

query

31 of 120

31

Page

Component1

query

query

query

32 of 120

32

Page

Component1

query

query

query

query

33 of 120

33

Page

Component1

Component100500

query

query

query

query

query

query

34 of 120

34

35 of 120

35

36 of 120

36

37 of 120

Зацепление (coupling)

37

Page

Component1

Component100500

query

query

query

query

query

query

38 of 120

38

39 of 120

39

query

query

query

query

query

query

query

query

query

query

query

query

query

query

query

query

query

query

40 of 120

40

query

query

query

query

query

query

query

query

query

query

query

query

query

query

query

query

query

query

41 of 120

🚌 Композиция

41

42 of 120

<div className="app">

<UserAvatar user={user} />

</div>

42

43 of 120

<div className="app">

<UserAvatar user={user} />

<Body footer={<Footer user={user} />}>

<Content />

</Body>

</div>

43

44 of 120

44

45 of 120

45

Page

Component1

Component100500

query

query

query

query

query

query

46 of 120

46

Page

Component1

Component100500

query

query

query

query

query

query

Page

Component1

query

47 of 120

47

Page

Component1

Component100500

query

query

query

query

query

query

Page

Component1

Component100500

query

Component100500 query={query}

query

Component100500 query={query}

48 of 120

🏎 Render function

48

49 of 120

function MyTodoList({ todos }) {

return (� ...

<ul>

{todos.map(item =>

<Todo todo={item} />

)}

</ui>

49

50 of 120

function MyTodoList({ todos }) {

return (

...� <ul>

{todos.map(item =>

<Todo todo={item} />

)}

</ul>

50

51 of 120

function MyTodoList({ todos }) {

return (� ...

<TodoList

render={todo => <Todo todo={todo} />}

/>

);

}

51

52 of 120

function MyTodoList({ todos }) {

return (� ...

<TodoList

render={todo => <Todo todo={todo} />)}

/>

);

}

52

53 of 120

👉 react-final-form

react-router

react-dropzone

react.Context

53

54 of 120

👉 react-final-form

👉 react-router

react-dropzone

react.Context

54

55 of 120

👉 react-final-form

👉 react-router

👉 react-dropzone

react.Context

55

56 of 120

👉 react-final-form

👉 react-router

👉 react-dropzone

👉 react.Context

56

57 of 120

57

Page

Component1

Component100500

query

query

query

query

query

query

Page

Component1

Component100500

query

Component100500 query={query}

query

Component100500 query={query}

58 of 120

58

Page

Component1

Component100500

query

query

query

query

query

query

Page

Component1

Component100500

query

query

() => <Component100500 query={query} />

() => <Component100500 … />

59 of 120

59

Page

Component1

Component100500

query

query

() => <Component100500 query={query}

() => <Component100500

Page

Component1

Component100500

query

Component100500 ...

query

Component100500 query={query}

60 of 120

60

61 of 120

61

62 of 120

🛸 Redux

62

63 of 120

63

64 of 120

64

connect(state => ({

query: state.query

}))(Clusters)

65 of 120

65

connect(state => ({

query: state.query

}))(Clusters)

66 of 120

redux ❤️ context

66

67 of 120

Inversion of Control

67

68 of 120

public class TmsBackofficeMiddleware {

private static final Logger LOGGER = LoggerFactory.getLogger("dms-msg");

private static final ZoneId MOSCOW = ZoneId.of("Europe/Moscow");

private static final Integer BATCH_SIZE = 1000;

@Inject

private TmsBackofficeService tmsBackofficeService;

68

69 of 120

public class TmsBackofficeMiddleware {

private static final Logger LOGGER = LoggerFactory.getLogger("dms-msg");

private static final ZoneId MOSCOW = ZoneId.of("Europe/Moscow");

private static final Integer BATCH_SIZE = 1000;

@Inject

private TmsBackofficeService tmsBackofficeService;

69

Не передаем в конструкторе или сеттером

70 of 120

<A>

<B>

<C>

<D />

</C>

</B>

</A>

70

71 of 120

<A>

<B>

<C>

<D />

</C>

</B>

</A>

connect(({vacancies}) => ({

vacancies,

}))(D)

71

72 of 120

<A>

<B>

<C>

<D />

</C>

</B>

</A>

connect(({vacancies}) => ({

vacancies,

}))(D)

function D({vacancies}) {

console.log(vacancies)

/**

* [

* {

* id: 1,

* name: 'Программист'

* }

* ]

*/

}

72

73 of 120

<A>

<B>

<C>

<D />

</C>

</B>

</A>

connect(({vacancies}) => ({

vacancies,

}))(D)

function D({vacancies}) {

console.log(vacancies)

/**

* [

* {

* id: 1,

* name: 'Программист'

* }

* ]

*/

}

73

74 of 120

Dependency Injection

74

75 of 120

render(

<Provider store={store}>

<App />

</Provider>,

document.querySelector('#root')

);

75

76 of 120

render(

<Provider store={store}>

<App />

</Provider>,

document.querySelector('#root')

);

76

77 of 120

render(

<Provider store={store}>

<App />

</Provider>,

document.querySelector('#root')

);

const store = React.createContext(store);�

render(<App />, document.querySelector('#root'));

77

78 of 120

render(

<Provider store={store}>

<App />

</Provider>,

document.querySelector('#root')

);

const store = React.createContext(store);

render(<App />, document.querySelector('#root'));

78

79 of 120

79

Page

Component1

Component100500

query

query

query

query

query

query

Page

query

Store

80 of 120

80

Page

Component1

Component100500

query

query

query

query

query

query

Page

query

Store

Component100500

81 of 120

81

Page

Component1

Component100500

query

query

query

query

query

query

Page

Connect

query

query

Store

Component100500

query

82 of 120

82

Page

Component1

Component100500

query

query

query

query

query

query

Page

Connect

query

query

Store

Component100500

query

83 of 120

83

Page

Component1

Component100500

query

query

query

query

query

query

Page

Connect

query

query

Store

Component100500

query

84 of 120

render(

<Provider store={store}>

<App />

</Provider>,

document.querySelector('#root')

);

const store = React.createContext(store);

render(<App />, document.querySelector('#root'));

84

85 of 120

🚀 Контекст

85

86 of 120

✨ Определяется для всего приложения

✨ Может быть получен из любого компонента

✨ Может быть переопределен для поддерева

86

87 of 120

87

88 of 120

88

89 of 120

89

90 of 120

✨ Определяется для всего приложения

✨ Может быть переопределен для ----поддерева

✨ Может быть получен из любого ----компонента

90

91 of 120

🐣 Использование контекста

91

92 of 120

👉 Иммутабельные данные

92

93 of 120

👉 Иммутабельные данные

👉 Дата-слой приложения

93

94 of 120

const Vacancy = React.createContext();

function VacancyProvider({ children }) {

const [ name, setName ] = useState('Frontend-dev');

const [ id ] = useState(123);

const value = {

data: { id, name },

changeName: (newName) => {

setName(newName);

}

};

return <Vacancy.Provider value={value}> {children} </Vacancy.Provider>;

}

94

95 of 120

const Vacancy = React.createContext();

function VacancyProvider({ children }) {

const [ name, setName ] = useState('Frontend-dev');

const [ id ] = useState(123);

const value = {

data: { id, name },

changeName: (newName) => {

setName(newName);

}

};

return <Vacancy.Provider value={value}> {children} </Vacancy.Provider>;

}

95

96 of 120

const Vacancy = React.createContext();

function VacancyProvider({ children }) {

const [ name, setName ] = useState('Frontend-dev');

const [ id ] = useState(123);

const value = {

data: { id, name },

changeName: (newName) => {

setName(newName);

}

};

return <Vacancy.Provider value={value}> {children} </Vacancy.Provider>;

}

96

97 of 120

const Vacancy = React.createContext();

function VacancyProvider({ children }) {

const [ name, setName ] = useState('Frontend-dev');

const [ id ] = useState(123);

const value = {

data: { id, name },

changeName: (newName) => {

setName(newName);

}

};

return <Vacancy.Provider value={value}> {children} </Vacancy.Provider>;

}

97

98 of 120

const Vacancy = React.createContext();

function VacancyProvider({ children }) {

const [ name, setName ] = useState('Frontend-dev');

const [ id ] = useState(123);

const value = {

data: { id, name },

changeName: (newName) => {

setName(newName);

}

};

return <Vacancy.Provider value={value}> {children} </Vacancy.Provider>;

}

98

99 of 120

const Vacancy = React.createContext();

function VacancyProvider({ children }) {

const [ name, setName ] = useState('Frontend-dev');

const [ id ] = useState(123);

const value = {

data: { id, name },

changeName: (newName) => {

setName(newName);

}

};

return <Vacancy.Provider value={value}> {children} </Vacancy.Provider>;

}

99

100 of 120

🔎 Где здесь dependency injection?

100

101 of 120

Уменьшаем зацепление

calculateChangedBits

101

102 of 120

102

const Vacancy = React.createContext();

...

function Vacancy() {

const { data } = useContext(Vacancy)

return data.name;

}

103 of 120

103

const Vacancy = React.createContext();

...

function Vacancy() {

const { data } = useContext(Vacancy)

return data.name;

}

104 of 120

Уменьшаем зацепление

⚙️ Внедряем полноценную модель

104

105 of 120

const Vacancy = React.createContext();

function VacancyProvider({ children }) {

const [ name, setName ] = useState('Frontend-dev');

const [ id ] = useState(123);

const value = {

data: { id, name },

changeName: (newName) => {

setName(newName);

}

};

return <Vacancy.Provider value={value}> {children} </Vacancy.Provider>;

}

105

106 of 120

const Vacancy = React.createContext();

function VacancyProvider({ children }) {

const [ name, setName ] = useState('Frontend-dev');

const [ id ] = useState(123);

const value = {

data: { id, name },

changeName: (newName) => {

setName(newName);

}

};

return <Vacancy.Provider value={value}> {children} </Vacancy.Provider>;

}

106

107 of 120

const Vacancy = React.createContext();

function VacancyProvider({ children }) {

const [ name, setName ] = useState('Frontend-dev');

const [ id ] = useState(123);

const value = {

data: { id, name },

changeName: (newName) => {

setName(newName);

}

};

return <Vacancy.Provider value={value}> {children} </Vacancy.Provider>;

}

107

108 of 120

🛠 Оптимизация контекста

108

109 of 120

109

110 of 120

👉 Мемоизация данных

calculateChangedBits

110

111 of 120

👉 Мемоизация данных

👉 calculateChangedBits

111

112 of 120

🤷🏼‍♀️ Как контекст не стоит использовать

112

113 of 120

👉 Ссылки на элементы которые нужны во время первого рендера

Данные, которые необходимо проверять в SCU или memo

113

114 of 120

👉 Ссылки на элементы которые нужны во время первого рендера

👉 Данные, которые необходимо проверять в SCU или memo

114

115 of 120

👩🏽‍💻 Подведем итоги?

115

116 of 120

⛓ Контекст решает проблему зацепления (coupling)

Контекст позволяет инжектить данные и методы

Контекст не работает со стандартными memo и SCU

Для оптимизации лучше использовать мемоизацию или calculateChangedBits

116

117 of 120

⛓ Контекст решает проблему зацепления (coupling)

🛠 Контекст позволяет инжектить данные и методы

Контекст не работает со стандартными memo и SCU

Для оптимизации лучше использовать мемоизацию или calculateChangedBits

117

118 of 120

⛓ Контекст решает проблему зацепления (coupling)

🛠 Контекст позволяет инжектить данные и методы

⚖️ Контекст не работает со стандартными memo и SCU

Для оптимизации лучше использовать мемоизацию или calculateChangedBits

118

119 of 120

⛓ Контекст решает проблему зацепления (coupling)

🛠 Контекст позволяет инжектить данные и методы

⚖️ Контекст не работает со стандартными memo и SCU

⚙️ Для оптимизации лучше использовать мемоизацию или calculateChangedBits

119

120 of 120

120

Ссылка на все ссылки: https://bit.ly/2MO9srZ

Мостовой Никита

�twitter: @xnimorz https://xnim.ru