Веб-приложение или сайт, который загружает только одну страницу и все последующие запросы обрабатываются без полной перезагрузки страницы
import express from 'express';
const app = express();
app.get('/notes', (req, res) => res.render('notes'));
app.all('*', (req, res) => res.sendStatus(404));
app.listen(8080);
$ npm install react-router-dom @types/react-router-dom
import { Fragment } from 'react';
import { BrowserRouter, Route } from 'react-router-dom';
function NotesApp() {
return (
<BrowserRouter>
<Fragment>
<Route path="/" component={HomePage} />
<Route path="/notes" component={NotesPage} />
</Fragment>
</BrowserRouter>
);
}
$ npm install react-router-dom @types/react-router-dom
import { Fragment } from 'react';
import { BrowserRouter, Route } from 'react-router-dom';
function NotesApp() {
return (
<BrowserRouter>
<Fragment>
<Route exact path="/" component={HomePage} />
<Route path="/notes" component={NotesPage} />
</Fragment>
</BrowserRouter>
);
}
import { Fragment } from 'react';
import { BrowserRouter, Route, Link } from 'react-router-dom';
function NotesApp() {
return (
<BrowserRouter>
<Fragment>
<nav>
<Link to="/home">Home</Link>
<Link to="/notes">Notes</Link>
</nav>
<Route exact path="/home" component={HomePage} />
<Route exact path="/notes" component={NotesPage} />
</Fragment>
</BrowserRouter>
);
}
import { Fragment } from 'react';
import { BrowserRouter, Route, Link } from 'react-router-dom';
function NotesApp() {
return (
<BrowserRouter>
<Fragment>
<nav>
<Link to="/home">Home</Link>
<Link to="/notes">Notes</Link>
</nav>
<Route exact path="/home" component={HomePage} />
<Route exact path="/notes" component={NotesPage} />
</Fragment>
</BrowserRouter>
);
}
import { Fragment } from 'react';
import { BrowserRouter, Route, NavLink } from 'react-router-dom';
function NotesApp() {
return (
<BrowserRouter>
<Fragment>
<nav>
<NavLink activeClassName="link_active" to="/home">
Home
</NavLink>
<NavLink activeClassName="link_active" to="/notes">
Notes
</NavLink>
</nav>
<Route exact path="/home" component={HomePage} />
<Route exact path="/notes" component={NotesPage} />
</Fragment>
</BrowserRouter>
);
}
import { Fragment } from 'react';
import { BrowserRouter, Route } from 'react-router-dom';
function NotesApp() {
return (
<BrowserRouter>
<Fragment>
<Route exact path="/notes/:id" component={NotePage} />
</Fragment>
</BrowserRouter>
);
}
import { RouteComponentProps } from 'react-router-dom';
type NotePageProps = RouteComponentProps<{ id: string }>;
function NotePage({ match }: NotePageProps) {
return <h1>Note id: {match.params.id}</h1>;
}
import { RouteComponentProps } from 'react-router-dom';
type NotePageProps = RouteComponentProps<{ id: string }>;
function NotePage({ match }: NotePageProps) {
return <h1>Note id: {match.params.id}</h1>;
}
import { RouteComponentProps } from 'react-router-dom';
type NotePageProps = RouteComponentProps<{ id: string }>;
function NotePage({ match }: NotePageProps) {
return <h1>Note id: {match.params.id}</h1>;
}
import { Fragment } from 'react';
import { BrowserRouter, Route } from 'react-router-dom';
function NotesApp() {
return (
<BrowserRouter>
<Fragment>
<Route exact path="/home" component={HomePage} />
<Route exact path="/notes" component={NotesPage} />
<Route exact path="/notes/:id" component={NotePage} />
<Route component={NotFoundPage} />
</Fragment>
</BrowserRouter>
);
}
import { Fragment } from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
function NotesApp() {
return (
<BrowserRouter>
<Switch>
<Route exact path="/home" component={HomePage} />
<Route exact path="/notes" component={NotesPage} />
<Route exact path="/notes/:id" component={NotePage} />
<Route component={NotFoundPage} />
</Switch>
</BrowserRouter>
);
}
class Notes extends Component {
...
render() {
if (this.state.loading) {
return 'Загрузка...';
}
if (this.state.error) {
return 'Что-то пошло не так';
}
return (
<div className="notes">
{this.state.notes.map(note => (
<Note key={note.id} name={note.name} text={note.text} />
))}
</div>
);
}
}
class Notes extends Component<NotesProps, NotesState> {
state: NotesState = {
loading: true,
error: false,
notes: null
}
componentDidMount() {
fetch('/api/notes')
.then(response => response.json())
.then(notes => {
this.setState({ notes, loading: false });
})
.catch(() => {
this.setState({ error: true, loading: false });
});
}
...
}
class Notes extends Component<NotesProps, NotesState> {
state: NotesState = {
loading: true,
error: false,
notes: null
}
componentDidMount() {
fetch('/api/notes')
.then(response => response.json())
.then(notes => {
this.setState({ notes, loading: false });
})
.catch(() => {
this.setState({ error: true, loading: false });
});
}
...
}
class Notes extends Component<NotesProps, NotesState> {
state: NotesState = {
loading: true,
error: false,
notes: null
}
componentDidMount() {
fetch('/api/notes')
.then(response => response.json())
.then(notes => {
this.setState({ loading: false, notes });
})
.catch(() => {
this.setState({ error: true, loading: false });
});
}
...
}
Функция, которая принимает функцию
в качестве аргумента или
возвращает функцию в качестве результата, называется функцией высшего порядка
function greaterThen(x) {
return function (y) {
return x > y;
}
}
const greaterThen10 = greaterThen(10);
greaterThen10(42); // true
function logArguments(f) {
return function (...args) {
const result = f(...args);
console.log('called with', args, 'result', result);
return result;
}
}
logArguments(Math.max)(1, 2, 3);
// called with [1, 2, 3] result 3
[...].map(n => ...);
[...].filter(n => ...);
[...].reduce((acc, n) => ...);
import React, { Component } from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
interface NoteOwnProps {
name: string;
text: string;
}
type NoteProps = NoteOwnProps & RouteComponentProps<{ id: string }>;
function Note({ name, text, match }: NoteProps) {
return <div>ID: {match.params.id}</div>;
}
const NoteWithRouter = withRouter(Note);
import React, { Component } from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
interface NoteOwnProps {
name: string;
text: string;
}
type NoteProps = NoteOwnProps & RouteComponentProps<{ id: string }>;
function Note({ name, text, match }: NoteProps) {
return <div>ID: {match.params.id}</div>;
}
const NoteWithRouter = withRouter(Note);
import React, { Component } from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
interface NoteOwnProps {
name: string;
text: string;
}
type NoteProps = NoteOwnProps & RouteComponentProps<{ id: string }>;
function Note({ name, text, match }: NoteProps) {
return <div>ID: {match.params.id}</div>;
}
const NoteWithRouter = withRouter(Note);
import React, { Component } from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
interface NoteOwnProps {
name: string;
text: string;
}
type NoteProps = NoteOwnProps & RouteComponentProps<{ id: string }>;
function Note({ name, text, match }: NoteProps) {
return <div>ID: {match.params.id}</div>;
}
const NoteWithRouter = withRouter(Note);
import React, { Component } from 'react';
function hoc(WrappedComponent) {
return class extends Component {
render() {
return <WrappedComponent {...this.props} />;
}
}
}
import React, { Component } from 'react';
function withData(url, WrappedComponent) {
export class extends Component {
state = { ... }
componentDidMount() { // Запрашиваем данные, используя полученный url
... // См. «Работа с API»
}
render() {
if (this.state.loading) {
return 'Загрузка...';
}
if (this.state.error) {
return 'Что-то пошло не так';
}
return <WrappedComponent data={this.state.data} {...this.props} />;
}
}
}
import React, { Component } from 'react';
function withData(url, WrappedComponent) {
export class extends Component {
state = { ... }
componentDidMount() { // Получаем данные, используя полученный url
... // См. «Работа с API»
}
render() {
if (this.state.loading) {
return 'Загрузка...';
}
if (this.state.error) {
return 'Что-то пошло не так';
}
return <WrappedComponent data={this.state.data} {...this.props} />;
}
}
}
import React, { Component } from 'react';
function withData(url, WrappedComponent) {
export class extends Component {
state = { ... }
componentDidMount() { // Получаем данные, используя полученный url
... // См. «Работа с API»
}
render() {
if (this.state.loading) {
return 'Загрузка...';
}
if (this.state.error) {
return 'Что-то пошло не так';
}
return <WrappedComponent data={this.state.data} {...this.props} />;
}
}
}
import React, { Component } from 'react';
function withData(url, WrappedComponent) {
export class extends Component {
state = { ... }
componentDidMount() { // Получаем данные, используя полученный url
... // См. «Работа с API»
}
render() {
if (this.state.loading) {
return 'Загрузка...';
}
if (this.state.error) {
return 'Что-то пошло не так';
}
return <WrappedComponent data={this.state.data} {...this.props} />;
}
}
}
import withData from './hoc/with-data';
// Данные просто приходят в пропсах
function Notes({ data }: NotesProps) {
return (
<div className="notes">
{data.map(note => (
<Note key={note.id} name={note.name} text={note.text} />
))}
<div>
);
}
// Оборачиваем Notes, чтобы получить данные
export default withData('/api/notes', Notes);
import Notes from './components/notes';
function NotesApp() {
// Используем как самый обычный компонент
return (
<div className="notes-app">
<Notes />
...
</div>
)
}
withData('/api/notes', Notes);
withData('/api/notes/???', Note);
withData('/api/notes', Notes);
withData(props => `/api/notes/${props.id}`, Note);
withData({
url: props => `/api/notes/${props.id}`,
propName: 'note',
headers: {
Authorization: 'OAuth ...'
}
}, Note);
withRouter(withData('/api/notes', Notes));
withSomethingElse(
withRouter(
withData('/api/notes', Notes)
)
);
// lodash, underscore, ramda ...
import { compose } from 'redux';
compose(f, g, h)(x) === f(g(h(x)));
withData('/api/notes')(Notes);
function withData(url) {
return function (WrappedComponent) {
return class extends Component {
...
}
}
}
compose(
withSomethingElse,
withRouter,
withData('/api/notes')
)(Notes);
function Notes() {
return (
<DataProvider
url="/api/notes"
render={notes => notes.map(note => (
<Note
key={note.id}
name={note.name}
text={note.text}
/>
))}
/>
);
}
function renderNotes(notes) {
return notes.map(note => (
<Note key={note.id} name={note.name} text={note.text} />
));
}
function Notes() {
return <DataProvider url="/api/notes" render={renderNotes} />;
}
class DataProvider extends Component<Props, State> {
state = { ... }
componentDidMount() { // Запрашиваем данные, используя полученный url
... // См. «Работа с API»
}
render() {
if (this.state.loading) {
return 'Загрузка...';
}
if (this.state.error) {
return 'Что-то пошло не так';
}
return this.props.render(this.state.data);
}
}
function Notes() {
return (
<DataProvider url="/api/notes">
{notes => notes.map(note => (
<Note
key={note.id}
name={note.name}
text={note.text}
/>
))}
</DataProvider>
);
}
function Notes() {
return (
<DataProvider url="/api/notes">
{({ data, error, loading }) => {
if (loading) {
return <LoadingScreen />;
}
...
return notes.map(note => (
<Note
key={note.id}
name={note.name}
text={note.text}
/>
));
}}
</DataProvider>
);
}
class DataProvider extends Component<Props, State> {
state = { ... }
componentDidMount() { // Запрашиваем данные, используя полученный url
... // См. «Работа с API»
}
render() {
return this.props.children(this.state);
}
}
<DataFetcher>
{data => (
<Actions>
{actions => (
<Translations>
{translations => (
<Styles>
{styles => ...}
</Styles>
)}
</Translations>
)}
</Actions>
)}
</DataFetcher>
const tabs = [
{
label: 'Books',
content: 'Dolore exercitation consequat sunt est anim occaecat.'
},
{
label: 'Films',
content: 'Cillum proident enim id pariatur minim reprehenderit.'
},
...
];
<Tabs tabs={tabs} />
<Tabs
tabs={tabs}
tabsPosition="bottom"
/>
<Tabs
tabs={tabs}
tabsPosition="bottom"
disabled={[ 1 ]}
/>
<Tabs
tabs={tabs}
tabsPosition="bottom"
disabled={[ 1 ]}
onTabChange={...}
activeTabClassName="..."
icons={[...]}
...
/>
<Tabs initialTabId="books">
<TabList>
<Tab id="books">Books</Tab>
<Tab id="films">Films</Tab>
</TabList>
<TabPanels>
<TabPanel id="books">
Dolore exercitation consequat sunt est anim occaecat.
</TabPanel>
<TabPanel id="films">
Cillum proident enim id pariatur minim reprehenderit.
</TabPanel>
</TabPanels>
</Tabs>
<Tabs initialTabId="books">
<TabPanels>
<TabPanel id="books">
Dolore exercitation consequat sunt est anim occaecat.
</TabPanel>
<TabPanel id="films">
Cillum proident enim id pariatur minim reprehenderit.
</TabPanel>
</TabPanels>
<TabList>
<Tab id="books">Books</Tab>
<Tab id="films">Films</Tab>
</TabList>
</Tabs>
<Tabs initialTabId="books">
<TabPanels>
<TabPanel id="books">
Dolore exercitation consequat sunt est anim occaecat.
</TabPanel>
<TabPanel id="films">
Cillum proident enim id pariatur minim reprehenderit.
</TabPanel>
</TabPanels>
<TabList>
<Tab id="books">Books</Tab>
<Tab id="films" disabled>Films</Tab>
</TabList>
</Tabs>
<select>
<optgroup label="Группа">
<option>Пункт 1</option>
<option>Пункт 2</option>
</optgroup>
<option>Пункт 3</option>
<option>Пункт 4</option>
</select>
// theme-context.ts
import { createContext } from 'react';
const ThemeContext = createContext('light');
export default ThemeContext;
import React from 'react';
import ThemeContext from './theme-context';
function Note() {
return (
<ThemeContext.Consumer>
{theme => ...}
</ThemeContext.Consumer>
);
}
import React, { Component } from 'react';
import ThemeContext from './theme-context';
class NotesApp extends Component {
...
render() {
return (
<ThemeContext.Provider value="dark">
...
</ThemeContext.Provider>
);
}
}
import React, { Component } from 'react';
import ThemeContext from './theme-context';
class NotesApp extends Component {
...
render() {
return (
<ThemeContext.Provider value={this.state.theme}>
...
</ThemeContext.Provider>
);
}
}
<Tabs initialTabId="books">
<TabList>
<Tab id="books">Books</Tab>
<Tab id="films">Films</Tab>
</TabList>
<TabPanels>
<TabPanel id="books">
Dolore exercitation consequat sunt est anim occaecat.
</TabPanel>
<TabPanel id="films">
Cillum proident enim id pariatur minim reprehenderit.
</TabPanel>
</TabPanels>
</Tabs>
import { createContext } from 'react';
const TabsContext = createContext({
activeTabId: '',
changeTab: () => {}
});
class Tabs extends Component<TabsProps, TabsState> {
state: TabsState = {
activeTabId: this.props.initialTabId
}
changeTab = (id: string) => this.setState({ activeTabId: id })
render() {
const contextValue = { activeTabId, changeTab: this.changeTab };
return (
<TabsContext.Provider value={contextValue}>
{children}
</TabsContext.Provider>
);
}
}
class Tabs extends Component<TabsProps, TabsState> {
state: TabsState = {
activeTabId: this.props.initialTabId
}
changeTab = (id: string) => this.setState({ activeTabId: id })
render() {
const contextValue = { activeTabId, changeTab: this.changeTab };
return (
<TabsContext.Provider value={contextValue}>
{children}
</TabsContext.Provider>
);
}
}
class Tabs extends Component<TabsProps, TabsState> {
state: TabsState = {
activeTabId: this.props.initialTabId
}
changeTab = (id: string) => this.setState({ activeTabId: id })
render() {
const contextValue = { activeTabId, changeTab: this.changeTab };
return (
<TabsContext.Provider value={contextValue}>
{children}
</TabsContext.Provider>
);
}
}
class Tabs extends Component<TabsProps, TabsState> {
state: TabsState = {
activeTabId: this.props.initialTabId
}
changeTab = (id: string) => this.setState({ activeTabId: id })
render() {
const contextValue = { activeTabId, changeTab: this.changeTab };
return (
<TabsContext.Provider value={contextValue}>
{children}
</TabsContext.Provider>
);
}
}
function TabList({ children }: TabListProps) {
return <div className="tab-list">{children}</div>;
}
function TabPanels({ children }: TabPanelsProps) {
return <div className="tab-panels">{children}</div>;
}
function Tab({ children, id }: TabProps) {
return (
<TabsContext.Consumer>
{context => {
function changeTab() {
context.changeTab(id);
}
return (
<div className="tab" onClick={changeTab}>
{children}
</div>
);
}}
</TabsContext.Consumer>
);
}
function TabPanel({ children, id }: TabPanelProps) {
return (
<TabsContext.Consumer>
{context => context.activeTabId === id
? <div className="tab-panel">{children}</div>
: null
}
</TabsContext.Consumer>
);
}
function SimpleTabs({ tabs }: SimpleTabsProps) {
return (
<Tabs>
<TabList>
{tabs.map(tab => (
<Tab id={tab.label}>{tab.label}</Tab>
))}
</TabList>
<TabPanels>
{tabs.map(tab => (
<TabPanel id={tab.label}>{tab.description}</TabPanel>
))}
</TabPanels>
</Tabs>
);
}
Компонент, который отлавливает ошибки в любом месте поддерева компонентов и реагирует на них
import React, { Component } from 'react';
class ErrorBoundary extends Component {
state = {
hasError: false
}
static getDerivedStateFromError(error) { // Здесь можно обновить state,
return { hasError: true }; // чтобы отобразить запасной UI
}
componentDidCatch(error, info) {
logError(error, info); // Здесь можно залогировать ошибку
}
...
}
import React, { Component } from 'react';
class ErrorBoundary extends Component {
state = {
hasError: false
}
static getDerivedStateFromError(error) { // Здесь можно обновить state,
return { hasError: true }; // чтобы отобразить запасной UI
}
componentDidCatch(error, info) {
logError(error, info); // Здесь можно залогировать ошибку
}
...
}
import React, { Component } from 'react';
class ErrorBoundary extends Component {
state = {
hasError: false
}
static getDerivedStateFromError(error) { // Здесь можно обновить state,
return { hasError: true }; // чтобы отобразить запасной UI
}
componentDidCatch(error, info) {
logError(error, info); // Здесь можно залогировать ошибку
}
...
}
import React, { Component } from 'react';
class ErrorBoundary extends Component {
...
render() {
// В случае ошибки можно отобразить запасной UI
if (this.state.hasError) {
return <h1>Что-то пошло не так</h1>;
}
return this.props.children;
}
}
import React, { Component } from 'react';
import ErrorBoundary from './error-boundary';
class App extends Component {
render() {
return (
<ErrorBoundary>
...
</ErrorBoundary>
);
}
}
$ mkdir notes-app && cd notes-app
$ npm install --save next react react-dom
// package.json
{
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
}
}
// ./pages/index.js
export default function IndexPage() {
return <h1>Welcome to next.js!</h1>;
}
$ npm run dev
DONE Compiled successfully span 1773ms
> Ready on http://localhost:3000
import { parse } from 'url';
import next from 'next';
import express from 'express';
const server = express();
const app = next({ dev: process.env.NODE_ENV !== 'production' });
const requestHandler = app.getRequestHandler();
app.prepare().then(() => {
server
.get('/notes', (req, res) => app.render(req, res, '/index'))
.get('/notes/:note', (req, res) => app.render(req, res, '/note'))
.get('*', (req, res) => requestHandler(req, res, parse(req.url, true)))
.listen(3000, () => console.log('Listening on http://localhost:3000'));
});
import { parse } from 'url';
import next from 'next';
import express from 'express';
const server = express();
const app = next({ dev: process.env.NODE_ENV !== 'production' });
const requestHandler = app.getRequestHandler();
app.prepare().then(() => {
server
.get('/notes', (req, res) => app.render(req, res, '/index'))
.get('/notes/:note', (req, res) => app.render(req, res, '/note'))
.get('*', (req, res) => requestHandler(req, res, parse(req.url, true)))
.listen(3000, () => console.log('Listening on http://localhost:3000'));
});
import { parse } from 'url';
import next from 'next';
import express from 'express';
const server = express();
const app = next({ dev: process.env.NODE_ENV !== 'production' });
const requestHandler = app.getRequestHandler();
app.prepare().then(() => {
server
.get('/notes', (req, res) => app.render(req, res, '/index'))
.get('/notes/:note', (req, res) => app.render(req, res, '/note'))
.get('*', (req, res) => requestHandler(req, res, parse(req.url, true)))
.listen(3000, () => console.log('Listening on http://localhost:3000'));
});
import { parse } from 'url';
import next from 'next';
import express from 'express';
const server = express();
const app = next({ dev: process.env.NODE_ENV !== 'production' });
const requestHandler = app.getRequestHandler();
app.prepare().then(() => {
server
.get('/notes', (req, res) => app.render(req, res, '/index'))
.get('/notes/:note', (req, res) => app.render(req, res, '/note'))
.get('*', (req, res) => requestHandler(req, res, parse(req.url, true)))
.listen(3000, () => console.log('Listening on http://localhost:3000'));
});