понедельник, 19 декабря 2022 г.

React Hooks

Как работают функциональные компоненты с хуками.


https://reactjs.org/docs/hooks-reference.html


---------------------------------------------------------------------

1) render

---------------------------------------------------------------------


Код тела функции компонента выполняется всегда полностью, включая объявление переменных и функций при:

- монтировании компонента,

- изменении на другие значения внешних props переданных в компонент, если значение не изменилось, то тело функции компонента не выполнится.

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

   то тело функции компонента не выполнится, ререндера не произойдет. Чтобы произошло выполнение тела функции при изменении на то же самое значение

   внутреннего state нужно выполнить такой хук:


function useForceUpdate () {

    const [value, setValue] = useState(0);

    return function () {

        setValue(function (previousValue) {

           return previousValue + 1;

        });

    };

}


const forceUpdate = useForceUpdate();


handleClick () {

    setStateValue(0);

    forceUpdate();

}


---------------------------------------------------------------------

2) hooks

---------------------------------------------------------------------


Хуки могут выноситься в отдельный функции, в другие файлы, но должны всегда вызываться тольк внутри функции компонента,

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



---------------------------------------------------------------------

3) useContext

---------------------------------------------------------------------


Позволяет получить значение context всем компонентам потомкам сразу без необходимости передачи значения по цепочке потомков в каждый props.


Компонент, вызывающий useContext, всегда будет перерендериваться при изменении значения контекста.


Когда ближайший <MyContext.Provider> над компонентом обновляется, этот хук вызовет повторный рендер с последним значением контекста,

переданным этому провайдеру MyContext.


Контекст использует сравнение по ссылкам, чтобы определить, когда запускать последующий рендер. Поэтому повторный рендер дочернего компонента будет производиться

при каждом повторном рендере родительского Provider-компонента, если передаваемый им объект будет создаваться каждый раз. Один из вариантов решения этой проблемы - это передача вариантов

контекст только данных примитивов из родительского компонента.


useContext(MyContext) позволяет только читать контекст и подписываться на его изменения.

Обязательно необходимо прописывать<MyContext.Provider> выше в дереве компонентов, чтобы предоставить значение для этого контекста.


import React, {useContext} from 'react';


import ReactDOM from 'react-dom';


const themes = {

    light: {

        background: "#ffffff"

    },

    dark: {

        background: "#000000"

    }

};


const ThemeContext1 = React.createContext(themes.light); // значение по умолчанию

const ThemeContext2 = React.createContext(themes.dark); // значение по умолчанию


function Child (): JSX.Element {


    const theme1 = useContext(ThemeContext1);

    const theme2 = useContext(ThemeContext2);


    return (

        <>

            <div>{theme1.background}</div>

            <br />

            <div>{theme2.background}</div>

        </>

    );


}


function Parent (): JSX.Element {

    return (

        <ThemeContext1.Provider value={themes.light}>

            <ThemeContext2.Provider value={themes.dark}>

                <Child />

            </ThemeContext2.Provider>

        </ThemeContext1.Provider>

    );

}


---------------------------------------------------------------------

4) useState

---------------------------------------------------------------------


Хранит в себе внутреннее состояние компонента.


Можно 1 раз задавать первоначальное значение при монтировании компонента.


Если первоначальное значение не задано, то оно устанваливается в undefined. При этом программа выполняется далее.


В качестве первоначального значения при монтировании компонента можно устанавливать значение, взятое из внешнего props.

Но в этом случае при последующем изменении внешннего значения из props внутреннее значение в state само обновляться не будет.


Для обновления внутреннего значения его нужно будет обновиться с помощью вызова функции setState внутри useEffect, который будет реагировать на изменение

значения из внешнего props.


Для ручного обновления значения state вызывается функция setState.


В setState можно передавать новое значение для state или функцию для сложного расчета обновления внутреннего значения.

Переданная функция для обновления также имеет доступ к предыдущему состоянию state обновляемого значения.


Функция setState не имеет функции callback, которая могла бы быть выполнена после обновления занчения и ререндеринга компонента.

Функией callback в таком случае является выполнение useEffect, который будет реагировать на изменения значения state.

На этой основе можно создать функцию, которая будет имитировать setState with callback.


Функция useState будет всегда будет возвращать последнее установленное значение.


const [value, setValue] = useState(0);


const [value, setValue] = useState(props.parentValue);


const [value, setValue] = useState(function () {

    return props.parentValue + 1;

});


function handleClick () {

    setValue(1);

}


function handleClick () {

    setValue(function (previousValue) {

        return previousValue + 1;

    });

}


useEffect(function () {

    setValue(props.parentValue);

}, [props.parentValue]);


function useStateWithCallback (initialState, alwayExecuteCallback) {


    const [state, setState] = useState(initialState);


    const callbackRef = useRef(undefined);


    const setStateAndCallback = useCallback(function setStateAndCallback (newState, callback) {

        callbackRef.current = callback;

        setState(newState);

    }, []);


    useEffect(function (): void {

        if (callbackRef.current) {

            callbackRef.current(state);

            callbackRef.current = undefined;

        }

    }, alwayExecuteCallback ? undefined : [state]);


    return [state, setStateAndCallback];


}


const [value, setValue] = useStateWithCallback(0, false);


function handleClick () {

    setValue(1, function (newState) {

        console.log(newState); // 1

    });

}


---------------------------------------------------------------------

5) useReducer

---------------------------------------------------------------------


Альтернатива для useState.

Принимает функцию редюсер типа (state, action) => newState и возвращает текущее состояние в паре с методом dispatch.


Хук useReducer обычно предпочтительнее useState, когда у вас сложная логика состояния, которая включает в себя несколько значений,

или когда следующее состояние зависит от предыдущего.


import React, {useReducer} from 'react';


const initialState = {count: 0};


function reducer (state, action) {

    switch (action.type) {

        case 'increment': return {count: state.count + 1};

        case 'decrement': return {count: state.count - 1};

        default: throw new Error();

    }

}


function Child (): JSX.Element {


    const [state, dispatch] = useReducer(reducer, initialState);


    function handleMinusClick () {

        dispatch({type: 'decrement'});

    }


    function handlePlusClick () {

        dispatch({type: 'increment'});

    }


    return (

        <>

            <div>Count: {state.count}</div>

            <button onClick={handleMinusClick}>-</button>

            <button onClick={handlePlusClick}>+</button>

        </>

    );


}


---------------------------------------------------------------------

6) useEffect

---------------------------------------------------------------------


Вызывается после завершения рендера.


Функция вызывает переданную в неё функцию каждый раз при монтировании компонента.


Затем, если в неё передан массив с зависимыми значениями, то функция вызывается каждый раз при последующих рендерингах при передаче props или изменении

внутреннего state.


Если передан массив с зависимыми значениями, то функция вызывается каждый раз при последующих рендерингах и изменении любого значения из массива.


Значения в массиве сравниваются поверхностно через строго равенство ===. Поэтому переданные в массив объекты всегда будут не равны своим предыдущим версиям.

В этом случае переданная в useEffect функция будет вызываться всегда.


Если функция useEffect возращает через return какую-то функцию, то она будет вызываться всегда при размонтировании компонента и какждый раз перед выполнением

основной переданной функции, когда прийдет момент вызова той.


В React 18 useEffect будет вызываться 2 раза, имитируя монтирование, размонтирование и повторное монтирование компонента.


Поэтому useEffect не рекомендуется использовать во всех возможных ситуациях.


// componentDidMount 1 раз

useEffect(function () {

    function log () {console.log(1);}

    log();

}, []);


// componentDidMount 1 раз и всегда componentDidUpdate

useEffect(function () {

    function log () {console.log(1);}

    log();

});


// componentDidMount 1 раз и componentDidUpdate, когда изменяется любое значение в массиве зависимостей

useEffect(function () {

    function log () {console.log(props.one + props.two);}

    log();

}, [props.one, props.two]);


// componentDidMount 1 раз и componentDidUpdate, когда изменяется любое значение в массиве зависимостей, или componentWillUnmount

useEffect(function () {


    function log () {console.log(props.one + props.two);}

    const intervalId = setInterval(function () {

        log();

    }, 1000);


    return function () { // componentWillUnmount

        clearInverval(intervalId);

    };


}, [props.one, props.two]);


---------------------------------------------------------------------

7) useLayoutEffect

---------------------------------------------------------------------


Идентичен useEffect, но в отличии от useEffect этот хук запускается синхронно после всех изменений DOM.

Используйте его для чтения макета из DOM и синхронного повторного рендеринга.

Обновления, запланированные внутри useLayoutEffect, будут полностью применены синхронно перед тем, как браузер получит шанс осуществить отрисовку.


Предпочитайте стандартный useEffect, когда это возможно, чтобы избежать блокировки визуальных обновлений.


Если вы используете серверный рендеринг, имейте в виду, что ни useLayoutEffect, ни useEffect не могут работать до загрузки JavaScript.

Вот почему React предупреждает, когда серверный компонент содержит useLayoutEffect.

Чтобы справиться с данной проблемой, либо переместите эту логику в useEffect (если она не нужна для первого рендера),

либо задержите отображение этого компонента до тех пор, пока не выполнится рендеринг на стороне клиента (если HTML некорректный до запуска useLayoutEffect).


// componentDidMount 1 раз

useLayoutEffect(function () {

    function log () {console.log(1);}

    log();

}, []);


// componentDidMount 1 раз и всегда componentDidUpdate

useLayoutEffect(function () {

    function log () {console.log(1);}

    log();

});


// componentDidMount 1 раз и componentDidUpdate, когда изменяется любое значение в массиве зависимостей

useLayoutEffect(function () {

    function log () {console.log(props.one + props.two);}

    log();

}, [props.one, props.two]);


// componentDidMount 1 раз и componentDidUpdate, когда изменяется любое значение в массиве зависимостей, или componentWillUnmount

useLayoutEffect(function () {


    function log () {console.log(props.one + props.two);}

    const intervalId = setInterval(function () {

        log();

    }, 1000);


    return function () { // componentWillUnmount

        clearInverval(intervalId);

    };


}, [props.one, props.two]);


---------------------------------------------------------------------

8) useInsertionEffect

---------------------------------------------------------------------


Аналогичен useEffect, но в отличии от useEffect, который запускается после изменения DOM, useInsertionEffect выполняется синхронно до изменения DOM.


Используется для вставки CSS стилей в DOM до того, как проичтается layout страницы.


useInsertionEffect не имеет доступ к ref и не может выполняться по расписанию.


useInsertionEffect должен использоваться только для создания библотек для css-in-js.


Вместо useInsertionEffect предпочтительней использовать useEffect или useLayoutEffect.


---------------------------------------------------------------------

9) useRef

---------------------------------------------------------------------


Используется дла хранения ссылки на тэг, как id и getElementById(), или для сохранения внутри значения переменной.


В случае передачи в тэг значению ref присваивается ссылка на этот тэг, прямой доступ к которой осуществляется через ref.current.

В ref тэга также можно передать функцию, которая будет устанавливать на него одновременно внутреннюю ссылку innerRef и внешнюю ссылку outerRef,

переданную из родительского компонента с помощью функции forwardRef.


Функция установки ref в тэг будет запускаться каждый раз после завершения рендеренга. Поэтому в ней можно прописать установку фокуса на какой-нибудь другой тэг

ref.current.parentElement.focus() или выполнить прокрутку scroll блока div.


В случае хранения в ref просто значения переменной прямой доступ к этому значения осуществляется через ref.current.


В useRef можно устанавливать 1 раз первоначально заданное значение. Оно само не будет обновляться при последующих рендренгах.


Для изменения значения ref нужно присвоить ей другое значение в ref.current.

Ручное изенение значения ref.current, в отличии от изменения внутреннего state или внешних props, не будет вызывать ререндеренг компонента.


Ref будет существовать тех пор, пока компонент не будет размонтирован.


// tag ref

const inputRef = useRef(null);


function handleClick () {

    inputRef.current.focus();

}


return <input type="text" ref={inputRef} />



// variable ref

const variableRef = useRef(100);


function handleClick () {

    variableRef.current = variableRef.current + 10; // 110

}


// append remove tag ref

const divRef = useRef(document.createElement('div'));


useEffect(function () {

    document.body.appendChild(divRef);


    return function () {

        document.body.removeChild(divRef);

    };

}, []);



// callback ref

function useStateWithCallback (initialState, alwayExecuteCallback) {


    const [state, setState] = useState(initialState);


    const callbackRef = useRef(undefined);


    const setStateAndCallback = useCallback(function setStateAndCallback (newState, callback) {

        callbackRef.current = callback;

        setState(newState);

    }, []);


    useEffect(function (): void {

        if (callbackRef.current) {

            callbackRef.current(state);

            callbackRef.current = undefined;

        }

    }, alwayExecuteCallback ? undefined : [state]);


    return [state, setStateAndCallback];


}


const [value, setValue] = useStateWithCallback(0, false);


function handleClick () {

    setValue(1, function (newState) {

        console.log(newState); // 1

    });

}


// inner, outer refs

const innerInputRef = useRef(null);


function setRef (element) {

    innerInputRef.current = element;

    if (outerInputRef) {

        if (typeof outerInputRef === 'function') {

            outerInputRef(element);

        } else {

            outerInputRef.current = element;

        }

    }

}


<input type="text" ref={setRef} />


---------------------------------------------------------------------

10) React.forwardRef

---------------------------------------------------------------------


Позволяет передать внешний Ref из родительского компонента в дочерний.


export const DivComponent: React.FC<Props> = forwardRef<HTMLDivElement, Props>(

    function DivComponent (props: Props, outerRef: ForwardedRef<HTMLDivElement>): JSX.Element {


        const innerRef: React.MutableRefObject<HTMLDivElement> = useRef<HTMLDivElement>(null);


        function setRef (element: HTMLDivElement): void {

            innerRef.current = element;

            if (outerRef) {

                if (typeof outerRef === 'function') {

                    outerRef(element);

                } else {

                    outerRef.current = element;

                }

            }

        }


        return (

            <div ref={setRef}>block</div>

        );


    }

);



---------------------------------------------------------------------

11) useCallback

---------------------------------------------------------------------


Во время выполнения тела функции компонента все его элементы внутри также пересоздаются.


Чтобы оптимизировать процесс пересоздания функций используется useCallback, который пересоздает тело функции только после изменения хотя бы одного из

значений в массиве зависимостей.


Функция всегда создается 1 раз при первом рендеренге компонента. И в дальнейшем пересоздается только, если изменились значения в массиве зависимостей.


const sendMessage = useCallback(function () {

    console.log(props.message + ' to ' + props.address);

}, [props.message, props.address]);


function handleClick () {

    sendMessage();

}


---------------------------------------------------------------------

12) useMemo

---------------------------------------------------------------------


Во время выполнения тела функции компонента все его элементы внутри также пересоздаются.


При этом вычисления значений переменных могут быть весьма затратны и длительны.


Чтобы оптимизировать процесс повторного вычисления значений переменных используется useMemo, которая заново вычисляет значение переменной только после изменения хотя бы одного из

значений в массиве зависимостей.


Переменная всегда создается 1 раз при первом рендеренге компонента. И в дальнейшем пересоздается только, если изменились значения в массиве зависимостей.


const bigArray = useMemo(function () {

    return new Array(1000).map(function (id) {

        return id + ') ' + props.message + ' to ' + props.address;

    });

}, [props.message, props.address]);


function handleClick () {

    console.log(bigArray);

}


---------------------------------------------------------------------

13) React.createPortal

---------------------------------------------------------------------


Помещает JSX-тэги внутрь обычного тэга.


interface Props {

    children: React.ReactNode;

}


function Portal (props: Props): JSX.Element {


    const container: React.MutableRefObject<HTMLDivElement> = useRef<HTMLDivElement>(document.createElement('div'));


    useEffect(function (): () => void {

        document.body.appendChild(container.current);

        const elementForRemoval: HTMLDivElement = container.current;


        return function (): void {

            document.body.removeChild(elementForRemoval);

        };

    }, [container]);


    return ReactDOM.createPortal(props.children, container.current);


}


---------------------------------------------------------------------

14) React.Children, React.cloneElement

---------------------------------------------------------------------


Позволяет обработать входящих props.children и изменить из props.


</div>

    {

        React.Children.map(props.children, function (child: React.ReactElement<TabProps>): React.ReactElement<TabProps> {

            return React.cloneElement(

                child,

                {

                    ...child.props,

                    selectedTabId: props.selectedTabId,

                    onChange: props.onChange

                }

            );

        })

    }

</div>


---------------------------------------------------------------------

15) useDebugValue

---------------------------------------------------------------------


Можно использовать в собственных хуках при отладке через React Dev Tools для вывода туда переданного в хук useDebugValue значения.


function useNetworkStatus () {


    const [isOnline, setIsOnline] = useState(null);


    useDebugValue(isOnline ? 'В сети' : 'Не в сети'); // В React Dev Tools выведется: NetworkStatus: "Не в сети"

    // useDebugValue(date, function (date) {return date.toDateString();}); // Можно передавать функцию для форматирования значения перед его выводом в React Dev Tools.


    return isOnline;


}


---------------------------------------------------------------------

16) useDeferredValue

---------------------------------------------------------------------


Делает отсрочку в применении обновления переданного значения например при быстром вводе символов в <input>


function Typeahead () {


    const query = useSearchQuery('');

    const deferredQuery = useDeferredValue(query);


    // useMemo позволяет обновить значение только когда изменится отложенное по времени deferredQuery, а не просто каждый query.

    const suggestions = useMemo(function () {

        <SearchSuggestions query={deferredQuery} />

    }, [deferredQuery]);


    return (

        <>

            <SearchInput query={query} />

            <Suspense fallback="Loading results...">{suggestions}</Suspense>

        </>

    );


}


---------------------------------------------------------------------

17) useTransition

---------------------------------------------------------------------


Возвращает функцию для запуска процесса перехода от одного состояния к другому и переменную, хранящую в себе статус состояния перехода.


function App () {


    const [count, setCount] = useState(0);


    const [isPending, startTransition] = useTransition();


    function handleClick () {

        startTransition(function () {

            setCount(function (previosCount) {

                return previosCount + 1;

            });

        });

    }


    return (

        <>

            <div>{isPending && <Spinner />}</div>

            <div>{count}</div>

            <button onClick={handleClick}>[+]</button>

        </>

    );

}


---------------------------------------------------------------------

18) useId

---------------------------------------------------------------------


Просто генерирует уникальные ID, которые стабильны на сервере и на клиенте для избегания несовпадения при гидрации.

useId нельзя использовать для генерирования ключей массивов.


function Fields () {

    const id = useId();

    return (

        <div>

            <label htmlFor={id + '-firstName'}>First Name</label>

            <input id={id + '-firstName'} type="text" />

            <br />

            <label htmlFor={id + '-lastName'}>Last Name</label>

            <input id={id + '-lastName'} type="text" />

        </div>

    );

}


---------------------------------------------------------------------

19) useSyncExternalStore

---------------------------------------------------------------------


useSyncExternalStore рекомендован для использования в качестве чтения и подписки на внешние источники данных.


Хук возвращает значение и принимает 3 аргумента:

- subscribe - функци для регистрации callback, который будет вызван при изменении данных в источнике данных store.

- getSnapshot - функция, которая вернет конкретное значение из источника данных store.

- getServerSnapshot - фукникция, которая вернет снимок snapshot в течении серверного рендеринга.


Когда производится серверный рендерингг, то вам надо сериализовать значение на сервере с помощью useSyncExternalStore.

React в этом случае используется snapshot в процессе hydration для предотвращения ошибок.


const selectedField = useSyncExternalStore(

    store.subscribe,

    function () {return store.getSnapshot().selectedField;},

    function () {return INITIAL_SERVER_SNAPSHOT.selectedField;}

);


---------------------------------------------------------------------

20) useOutsideClickEventHandler

---------------------------------------------------------------------


import React, {useEffect} from 'react';


export function useOutsideClickEventHandler <T extends HTMLElement = HTMLElement> (ref: React.RefObject<T>, handler: (event: Event) => void): void {

    useEffect(function (): () => void {


        function handleClickOutside (event: Event): void {

            if (ref.current && !ref.current.contains(event.target as Node)) {

                handler(event);

            }

        }


        document.addEventListener('mousedown', handleClickOutside);


        return function (): void {

            document.removeEventListener('mousedown', handleClickOutside);

        };


    }, [ref, handler]);

}


---------------------------------------------------------------------

21) useReduxState

---------------------------------------------------------------------


import {useSelector} from 'react-redux';


import get from 'lodash-es/get';


import {RootStateInterface} from './interfaces';


type RootStateKey = keyof RootStateInterface;

type StateKeyWithDots = `.${string}`;

type RootOrDotsStateKey = RootStateKey | StateKeyWithDots;


interface SelectedStateInterface extends Partial<RootStateInterface> {

    [key: StateKeyWithDots]: any;

}


export function useReduxState (firstStateKey: RootOrDotsStateKey, ...stateKeys: RootOrDotsStateKey[]): SelectedStateInterface {

    stateKeys.push(firstStateKey);

    const selectedState: SelectedStateInterface = {};

    stateKeys.forEach(function (stateKey: RootOrDotsStateKey): void {

        selectedState[stateKey] = useSelector(function (state: RootStateInterface): any {

            if (stateKey.indexOf('.') === 0) {

                return get(state, stateKey.slice(1));

            }

            return state[stateKey];

        });

    });

    return selectedState;

}


const {

    input: {

        value: inputValue,

        date: calendarDate

    }

} = useReduxState('calendar');

среда, 26 октября 2022 г.

React Extend Children Props | React cloneElement

 import React from 'react';


function RootComponent () {

    return (

        <ParentComponent>

            <ChildComponent a="1" b="2" />

            <ChildComponent a="3" b="4" />

        </ParentComponent>

    );

}


function ChildComponent (props: ChildComponentProps) {

    return (

        <div>{JSON.stringify(props)}</div>

    );

}


function ParentComponent (props: ParentComponentProps) {


    /* 

    // Variant 1.

    return (

        <div>{

            React.Children.map(props.children, function (child: React.ReactElement<ChildComponentProps>): React.ReactElement<ChildComponentProps> {

                return React.cloneElement(

                    child, 

                    {

                        a: child.props.a + 1,

                        b: child.props.b + 2,

                        key: child.props.a + 1

                    },

                    null

                );

            })

        }</div>

    );

    */


    /*

    // Variant 2.

    return (

        <div>{

            React.Children.map(props.children, function (child: ChildComponent): ChildComponent {

                return (

                    <ChildComponent

                        {...child.props} 

                        {...{

                            a: child.props.a + 1,

                            b: child.props.b + 2,

                            key: child.props.a + 1

                        }} 

                     />

                );

            })

        }</div>

    );

    */


    // Variant 3.

    return (

        <div>{

            React.Children.map(props.children, function (child: React.ReactElement<ChildComponentProps>): React.ReactElement<ChildComponentProps> {

                return (

                    <child.type

                        {...child.props} 

                        {...{

                            a: child.props.a + 1,

                            b: child.props.b + 2,

                            key: child.props.a + 1

                        }} 

                     />

                );

            })

        }</div>

    );


}

React Modal Portal

Вариант с классом.


import React from 'react';

import ReactDOM from 'react-dom';


class Modal extends React.Component {


    modalDiv = undefined;


    constructor (props) {

        super(props);

        this.modalDiv = document.createElement('div');

    }


    render () {

        return ReactDOM.createPortal(

            this.props.children,

            this.modalDiv

        );

    }


    componentDidMount () {

        document.body.appendChild(this.modalDiv);

    }


    componentWillUnmount () {

        document.body.removeChild(this.modalDiv);

    }


}


class Parent extends React.Component {


    constructor (props) {

        super(props);

    }


    render () {

        return (

            <Modal>

                <Child />

            </Modal>

        );

    }


}


class Child extends React.Component {


    constructor (props) {

        super(props);

    }


    render () {

        return (

            <div className="modal-message">

                <h1>Hello</h1>

            </div>

        );

    }


}


Результат


<html>

    <body>

        <div id="app"></div>

        <div>

            <div class="modal-message">

                <h1>Hello</h1>

            </div>

        </div>

    </body>

</html>


Вариант с функцией.


import React, {useEffect, useState} from 'react';

import ReactDOM from 'react-dom';


interface PortalProps {

    children: React.ReactNode;

}


function Portal (props: PortalProps): JSX.Element {


    const [div] = useState(function (): HTMLDivElement {

        return document.createElement('div');

    });

    

    useEffect(function (): () => void {

    

        document.body.appendChild(div);

        

        return function (): void {

            document.body.removeChild(div);

        };

        

    }, [div]);

    

    return ReactDOM.createPortal(props.children, div);

    

}


interface ModalProps {}


function Modal (props: ModalProps): JSX.Element {

    return (

        <Portal>

            <div className="modal-message">

                <h1>Hello</h1>

            </div>

        </Portal>

    );

}


<html>

    <body>

        <div id="app"></div>

        <div>

            <div class="modal-message">

                <h1>Hello</h1>

            </div>

        </div>

    </body>

</html>

среда, 30 марта 2022 г.

Node.js NPM очистить npm cache и изменить местоположение папки npm-cache

По умолчанию в Windows папка "npm-cache" размещается по пути:

%AppData%/npm-cache

Например по пути: C:\Users\Boris\AppData\Roaming\npm-cache

Удалить содержимое папки "npm-cache" можно с помощью команды:

npm cache clean --force

Также папку "npm-cache" можно просто удалить вручную.

В MacOS папка с кэшем размещается по пути: ~/.npm

При инсталляции библиотек можно задать другую папку для кэширования с помощью команды:

npm install --cache ./my-npm-cache-folder

Также можно задать другую папку для кэширования установленных библиотек глобально с помощью команды:

npm config set cache [your custom path] --global

Пример для Windows: npm config set cache D:\work\my-npm-cache-folder --global

Пример для MacOs: npm config set cache /Users/Boris/Desktop/my-npm-cache-folder --global

Если путь до папки содержит пробелы, то оберните его в двойные кавычки (" ").

Проверить состояние кэша можно командой:

npm cache verify

четверг, 17 марта 2022 г.

Convert original file to base64 code

 <!DOCTYPE html>

<html>

<body>


<p>Base 64 string here. You might want to trim.</p>


<input type="file" onchange="previewFile()"><br>


<script>


function previewFile () {

    var file = document.querySelector('input[type=file]').files[0];

    var reader = new FileReader();


    reader.addEventListener('load', function () {

        document.querySelector('p').innerHTML=reader.result; // convert file to base64 string using reader.result

    }, false);


    if (file) {

        reader.readAsDataURL(file);

    }

}


</script>


</body>

</html>

Node.js Convert file in base64 code to original file

 var fs = require('fs');


var base64 = 'MTIz'; // String with ZIP source in base64.


var path = './original.zip'

    , buffer = new Buffer(base64, 'base64');


fs.open(path, 'w', function (error, file) {

    if (error) {

        throw 'Error opening file: ' + error;

    }

    fs.write(file, buffer, 0, buffer.length, null, function (error) {

        if (error) throw 'Error writing file: ' + error;

        fs.close(file, function () {

            console.log('File written');

        })

    });

});

четверг, 29 июля 2021 г.

OpenSSL Шпаргалка по командам

Как работает HTTPS.

Сервер создает свой приватный ключ.
С помощью приватного ключа сервер создает запрос на подпись публичного сертификата, который должен будет подписать удостоверяющий центр. В запросе содержится публичный ключ и данные о владельце сервера. Эти данные будут являться частью сертификаты и будут доступными для просмотра.
Запрос на подпись публичного сертификата отправляется в удостоверяющий центр.
Удостоверяющий центр подписывает запрос с помощью своего приватного ключа и создает для сервера его публичный сертификат.
Браузер подключается к серверу через HTTPS.
Сервер отправляет в браузер свой публичный сертификат.
Браузер проверяет в удостоверяющем центре, что полученный публичный сертификат принадлежит серверу.
Если это подтверждается, то браузер зашифровывает публичным сертификатом свой ключ для симметричного шифрования и отправляет его серверу.
Сервер с помощью своего приватного ключа расшифровывает ключ для симметричного шифрования, полученный от браузера.
Далее при обмене данными браузер и сервер будут шифровать их с помощью этого симметричного ключа шифрования, который теперь есть у обоих.

Файл OpenSSL входит в состав Git и находится в папке: 

"C:\Program Files\Git\usr\bin\openssl.exe"

Узнать версию OpenSSL: 

openssl.exe version -a

Узнать набор команд OpenSSL

openssl.exe help

Список всех команд OpenSSL:

asn1parse         ca                   ciphers           cms          
crl                    crl2pkcs7        dgst               dhparam
dsa                   dsaparam        ec                  ecparam
enc                   engine             errstr             gendsa
genpkey           genrsa             help               list
nseq                 ocsp                passwd          pkcs12
pkcs7               pkcs8              pkey              pkeyparam
pkeyutl            prime              rand               rehash
req                   rsa                   rsautl             s_client
s_server           s_time            sess_id           smime
speed               spkac              srp                 storeutl
ts                     verify              version           x509

Получить список доступных алгоритмов шифрования:

openssl.exe ciphers -v 'ALL:COMPLEMENTOFALL'

Создание приватного ключа с сохранением его в файл private.key, алгоритмом шифрования RSA, размером ключа 2048 бит, защитой aes-128-cbc: 

openssl.exe genpkey \
-out private.key \
-algorithm RSA \
-pkeyopt rsa_keygen_bits:2048 \
-aes-128-cbc
..........................................+++++
...................................................................+++++
Enter PEM pass phrase: ************
Verifying - Enter PEM pass phrase: ************

При создании ключа можно задать пароль для доступа к нему - passphrase, например: 12345.

Для создания ключа лучше использовать алгоритм RSA с размером ключа 2048 бит.

Содержимое созданного приватного ключа в файле private.key выглядит так:

-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQInW7GrFjUhUcCAggA
MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBBn8AErtRKB9p7ii1+g2OhWBIIE
0MnC2dwGznZqpTMX0MYekzyxe4dKlJiIsVr1hgwmjFifzEBs/KvHBV3eIe9wDAzq
[21 lines removed...]
IfveVZzM6PLbDaysxX6jEgi4xVbqWugd9h3eAPeBv9Z5iZ/bZq5hMbt37ElA2Rnh
RfmWSzlASjQi4XAHVLCs6XmULCda6QGvyB7WXxuzbhOv3C6BPXR49z6S1MFvOyDA
2oaXkfS+Ip3x2svgFJj/VpYZHUHwRCzXcDl/CdVg9fxwxcYHuJDH16Qfue/LRtiJ
hqr4fHrnbbk+MZpDaU+h4shLRBg2dONdUEzhPkpdOOkF
-----END ENCRYPTED PRIVATE KEY-----

Посмотреть расшифрованное содержимое приватного ключа из файла private.key можно так:

openssl.exe pkey -in private.key -text -noout

Enter pass phrase for private.key: ****************

RSA Private-Key: (2048 bit, 2 primes)
modulus:
    00:be:79:08:22:1a:bc:78:3c:17:34:4a:d3:5f:2b:
    [...]
publicExponent: 65537 (0x10001)
privateExponent:
    10:20:95:54:b5:e8:d1:51:5d:31:9b:48:4c:5d:90:
    [...]
prime1:
    00:f5:3f:74:cf:ef:8f:93:e9:54:b3:79:a1:f2:91:
    5a:7e:15:13:26:f7:f9:d7:a8:f3:f9:6b:2b:90:93:
    57:54:cc:84:c9:ea:6f:9f:39:ad:ad:60:4c:f0:68:
    16:db:1a:49:51:56:87:f1:70:ae:c9:42:89:2a:38:
    55:3e:17:a0:78:a7:52:49:10:79:cf:99:ae:53:c8:
    e0:60:5d:7e:91:26:86:3b:79:d2:70:c0:39:38:dd:
    ed:ee:75:c0:15:c6:30:51:00:a8:93:f3:8b:25:01:
    04:25:72:fc:9c:e9:73:d0:93:11:2d:82:e2:e3:d0:
    66:c0:36:2f:b6:de:de:0d:47
prime2:
    00:c6:d2:ce:66:b5:35:6b:35:d7:bb:b0:e3:f4:2d:
    [...]
exponent1:
    00:e9:2e:e9:b9:5f:f5:2b:54:fa:c5:1f:4c:7d:5f:
    [...]
exponent2:
    00:83:ea:bc:ad:a2:cf:a5:a9:9c:d0:d8:85:f6:ae:
    [...]
coefficient:
    68:18:a7:4f:aa:86:a7:e0:92:49:76:8d:24:65:fa:
    [...]

Вытаскивание отдельного публичного ключа из приватного ключа файла private.key в отдельный файл public.key:

openssl.exe pkey -in private.key -pubout -out public.key

Enter pass phrase for private.key: ****************

Содержимое извлеченного публичного ключа в файле public.key выглядит так:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvnkIIhq8eDwXNErTXytD
U1JGrYUgFsN8IgFVMJmAuY15dBvSCO+6y9FA0H08utJVtHScyWeOlo1uo0TQ3RWr
Pe7W3O2SaW2gIby2cwzGf/FBExZ+BCNXkN5z8Kd38PXDLt8ar+7MJ3vrb/sW7zs2
v+rtfRar2RmhDPpVvI6sugCeHrvYDGdA/gIZAMMg3pVFivPpHnTH4AR7rTzWCWlb
nCB3z2FVYpvumrY8TvIo5OioD2I+TQyvlxDRo14QWxIdZxvPcCUxXMN9MC8fBtLu
IlllDmah8JzF2CF5IxVgVhi7hyTtSQfKsK91tAvN30F9qkZNEpjNX37M5duHUVPb
tQIDAQAB
-----END PUBLIC KEY-----

Имея приватный ключ можно создать запрос на подпись публичного сертификата. В запросе содержится публичный ключ и данные о владельце сервера. Эти данные будут являться частью сертификаты и будут доступными для просмотра.

Создание запроса на подпись публичного сертификата с использованием приватного ключа private.key и записью публичного сертификата в файл sign-request.csr:

openssl.exe req -new -key private.key -out sign-request.csr

Enter pass phrase for private.key: ****************

You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.

-----

Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:.
Locality Name (eg, city) []:New Yourk
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Your Company Name Ltd
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:www.your-site.com
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

Получившийся запрос на подпись публичного сертификата в файле sign-request.csr можно подписать самому с помощью приватного ключа (в этом случае получится самоподписанный сертификат) или отправить на подпись в удостоверяющий центр, чтобы получить публичный сертификат.

Посмотреть содержимое запроса на подпись публичного сертификата из файла sing-request.csr можно так:

openssl.exe req -text -in sign-request.csr -noout

Certificate Request:
    Data:
        Version: 1 (0x0)
        Subject: C = US, L = New Yourk, O = Your Company Name Ltd, CN = www.your-site.com
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:8a:d5:de:69:30:c7:77:b0:a0:54:f7:b3:34:9a:
                    96:1c:23:81:e3:9c:0c:81:a6:8a:a5:14:76:f4:4c:
                    b3:10:cb:ee:50:d1:ea:70:e9:7f:8f:75:67:f9:12:
                    83:b0:11:e7:6c:64:de:bc:af:bd:3f:43:da:b8:41:
                    96:75:34:63:85
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        Attributes:
            a0:00
    Signature Algorithm: ecdsa-with-SHA256
         30:44:02:20:52:b9:cf:ca:d1:25:1c:b7:57:65:fb:24:5d:95:
         15:f0:39:79:36:6c:d6:0a:42:6e:26:7c:54:e8:71:17:a5:99:
         02:20:5a:e0:cd:b3:60:ec:2c:fc:29:8c:f9:21:01:08:9a:a3:
         0d:fc:9a:d3:4f:24:fb:23:4f:c6:d7:a2:14:d1:54:f9

Создание запроса на подпись публичного сертификата на основе уже существующего публичного сертификата из файла public.crt:

openssl.exe x509 -x509toreq -in public.crt -out sign-request.csr -signkey private.key

Данную процедуру удобно выполнить, если вы хотите просто обновить публичный сертификат с истекшим сроком действия.

Создание запроса на подпись публичного сертификата на основе данных из конфигурационного файла config.cnf:

openssl.exe req -new -config config.cnf -key private.key -out sing-request.csr

Данную процедуру удобно выполнить, если вы не хотите создавать запрос на подпись в интерактивном режиме, вводя данные вручную так, как делали это выше по тексту.

Данные, находящиеся в файле config.cnf:

[req]
prompt = no
distinguished_name = dn
req_extensions = ext
input_password = PASSPHRASE

[dn]
CN = www.your-site.com
emailAddress = webmaster@www.your-site.com
O = Your Company Name Ltd
L = New Your
C = US

[ext]
subjectAltName = DNS:www.your-site.com,DNS:your-site.com

Создание самоподписанного публичного сертификата с помощью подписи запроса на подпись из файла sign-request.csr своим же приватным ключом private.key:

openssl.exe x509 -req -days 365 -in sign-request.csr -signkey private.key -out public.crt

Signature ok
subject=C = US, L = New Your, O =Your Company Name Ltd, CN = www.your-site.com
Getting Private key
Enter pass phrase for private.key: ****************

Создание самоподписанного публичного сертификата без промежуточного создания запроса на подпись публичного сертификата, используя только приватный ключ private.key:

openssl.exe req -new -x509 -days 365 -key private.key -out public.crt

Чтобы сразу ввести всю информацию с данными владельца сервера можно выполнить такую команду:

openssl.exe req -new -x509 -days 365 -key private.key -out public.crt \
 -subj "/C=US/L=New York/O=Your Company Name Ltd/CN=www.your-site.com"

Создание публичного сертификата, который может использоваться сразу для нескольких сайтов:

Сперва поместите дополнительную информацию об адресах сайтов в файл hostnames.txt:

subjectAltName = DNS:*.your-site.com, DNS:your-site.com

Создайте публичный сертификат с использованием файла hostnames.txt:

openssl.exe x509 -req -days 365 \
-in sign-request.csr -signkey private.key -out public.crt \
-extfile hostnames.ext

Если вы посмотрите на содержимое созданного публичного сертификата в файле public.crt, то так вы увидите новую секцию с данными ваших адресов сайтов:

X509v3 extensions:
    X509v3 Subject Alternative Name: 
        DNS:*.your-site.com, DNS:your-site.com

Просмотр содержимого публичного сертификата из файла public.crt:

openssl x509 -text -in public.crt -noout

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            76:bc:fb:f6:06:0e:61:eb:99:5e:83:ea:ef:92:0b:32:4f:fd:3b:51
        Signature Algorithm: ecdsa-with-SHA256
        Issuer: C = US, L = New York, O = Your Company Name Ltd, CN = www.your-site.com
        Validity
            Not Before: Aug 15 09:31:54 2020 GMT
            Not After : Aug 15 09:31:54 2021 GMT
        Subject: C = US, L = New York, O = Your Company Name Ltd, CN = www.your-site.com
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:8a:d5:de:69:30:c7:77:b0:a0:54:f7:b3:34:9a:
                    96:1c:23:81:e3:9c:0c:81:a6:8a:a5:14:76:f4:4c:
                    b3:10:cb:ee:50:d1:ea:70:e9:7f:8f:75:67:f9:12:
                    83:b0:11:e7:6c:64:de:bc:af:bd:3f:43:da:b8:41:
                    96:75:34:63:85
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        X509v3 extensions:
            X509v3 Subject Alternative Name: 
                DNS:*.your-site.com, DNS:your-site.com
    Signature Algorithm: ecdsa-with-SHA256
         30:45:02:20:4d:36:34:cd:e9:3e:df:18:52:e7:74:c4:a1:97:
         91:6a:e7:c1:6d:12:01:63:d1:fd:90:28:32:70:24:5c:be:35:
         02:21:00:bd:02:64:c9:8b:27:8f:79:c7:a4:41:7c:31:2f:98:
         29:3e:db:8c:f3:f1:d7:bb:fa:fe:95:48:be:16:e1:ab:1b

Самоподписанные публичные сертификаты содержат в себе меньше данных, чем тем, что были подписаны и созданы удостоверяющим центром.

Приватные ключи и публичные сертификаты могут создаваться в разных форматах.

Самые распространённые форматы:

Binary (DER) certificate - содержит X.509 сертификат в сырой форме, используя кодировку DER ASN.1

ASCII (PEM) certificate(s) - содержит DER-сертификат в кодировке Base64, который начинается с заголовка  -----BEGIN CERTIFICATE----- и заканчивается подвалом  -----END CERTIFICATE----- . Обычно встречается в виде одного сертификата, находящегося в одном файле.

Legacy OpenSSL key format - содержит приватный ключ в сырой форме, используя кодировку DER ASN.1

ASCII (PEM) key - содержит DER-приватный ключ в кодировке Base64 и иногда некоторые метаданные (например алгоритм, использующийся для защиты паролем). Текст в заголовке и подвале ключа могут отличаться в зависимости от того, какой формат ключа использовался при создании этого ключа.

PKCS#7 certificate(s) - комплексный формат, созданный для транспортировки подписанных и зашифрованных данных, описанный в "RFC 2315". Обычно встречается в виде файлов с расширением ".p7b" и."p7c" и может включать в себя всю цепочку сертификатов при необходимости. Этот формат поддерживается утилитой "Java keytool".

PKCS#8 key - новый формат для хранения приватного ключа, описанный в "RFC 5208". Если вам по какой-либо причине необходимо преобразовать PKCS # 8 в устаревший формат, используйте команду pkcs8.

PKCS#12 (PFX) key and certificate(s) - комплексный формат, который может хранить в себе и защищать серверный приватный ключ вместе со всей цепочкой сертификатов. Он обычно встречается в виде файлов с расширением ".p12" и ".pfx". Этот формат обычно используется в программах Microsoft, но также используется в качестве клиентских сертификатов. В наши дни имя PFX используется как синоним PKCS#12 не смотря на то, что название PFX имеет отношение к другому старому формату (ранней версии PKCS#12). Маловероятно, что вы где-то столкнетесь со старой версией.

Конвертация форматов PEM и DER.

Конвертировать сертификат из формата PEM в DER:

openssl.exe x509 -inform PEM -in public.pem -outform DER -out public.der

Конвертировать сертификат из формата DER в PEM:

openssl.exe x509 -inform DER -in public.der -outform PEM -out public.pem

Конвертировать PEM в PKCS#12PKCS#12 (PFX):

openssl.exe pkcs12 -export \
    -name "My Certificate" \
    -out public.p12 \
    -inkey private.key \
    -in public.crt \
    -certfile intermediate-certificates-chain.crt

Enter Export Password: ****************
Verifying - Enter Export Password: ****************

Конвертировать PKCS#12PKCS#12 (PFX) в PEM:

openssl pkcs12 -in public.p12 -out public.pem -nodes

После этого нужно открыть в редакторе файл public.pem и вручную отделить индивидуальный приватный ключ, сертификат и файлы промежуточных сертификатов.

OpenSSL также может выделить нужные компоненты для вас с помощью следующих команд:

openssl.exe pkcs12 -in public.p12 -nocerts -out private.key -nodes
openssl.exe pkcs12 -in public.p12 -nokeys -clcerts -out public.crt
openssl.exe pkcs12 -in public.p12 -nokeys -cacerts -out intermediate-certificates-chain.crt

Конвертировать PEM в PKCS#7:

openssl.exe crl2pkcs7 -nocrl -out public.p7b -certfile public.crt -certfile intermediate-certificates-chain.crt

Конвертировать  PKCS#7 в PEM:

openssl.exe pkcs7 -in public.p7b -print_certs -out public.pem

Также, как и для конвертации в формат PKCS#12 вы должны будете открыть в редакторе файл public.pem, чтобы вручную отделить индивидуальный приватный ключ, сертификат и файлы промежуточных сертификатов.

Создание собственного удостоверяющего центра.

Процесс создания собственного удостоверяющего центра описан на сайте https://www.feistyduck.com/library/openssl-cookbook/online/ch-openssl.html в разделе "Creating a Root CA".