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

#javascript #reactjs

Вопрос:

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

Однако я столкнулся с проблемой, когда, когда я возвращаю функцию ( play ) из своего крючка, а затем вызываю эту функцию, она обновляет данные на основе начального состояния крючка (несмотря на то, что состояние обновлялось много раз).

 const usePlayer = () => {
    const audioRef = useRef(__GLOBAL_AUDIO);
    const [state, setState] = useReducer((state, newState) => ({...state, ...newState}), {
        mounted: false,
        playing: false,
    });

    const play = () => ensureMounted(() => { 
        audioRef.current.play(); 
    });
    const ensureMounted = callback => {
        // This if statement will never run as this function does not 
        // seem to get any state other than the initially supplied one
        if(state.mounted){
            callback();
            return;
        }

        // ensureMounted will always skip to this portion of the function
        // despite what the current state is
        audioRef.current.src = cdn("/link/to/new/src.mp3");
        onLoad(callback);
    };

    useEffect(() => {
        // This function is tied to redux, but is irrelevant. Just know 
        // that mounted does change from true to false and vice-versa - often.
        const _mounted = checkToSeeIfMounted();
        setState({ mounted: _mounted });
    }, deps);

    return {
        playing: state.playing,
        play: play,
    };
};
 

Теперь очевидно, что этот код был чрезвычайно упрощен, но я надеюсь, что он все еще отражает проблему, с которой я сталкиваюсь. У меня есть подозрение, что проблема заключается в том, что React может передавать старую(?) ссылку на функцию при каждом повторном рендеринге, но я не уверен, а официальные документы настолько расплывчаты в отношении «продвинутых» функций, что я не уверен, как их использовать или будут ли они вообще полезны.

Любая помощь приветствуется, ура.

Комментарии:

1. В чем ваша проблема?

2. @Viet Мне кажется, что я как можно яснее объяснил, в чем моя проблема.

3. Я тоже так не думаю. Что, по-твоему , должно произойти, чего нет? Почему вы думаете, что это должно произойти, а не что происходит? Также, почему вы полностью изменили этот вопрос — у него был рабочий пример, и вы от него избавились.

4. @Jamiec Я думаю , что должно произойти, когда состояние будет обновлено (т. Е. когда mounted обновляется), функция должна действовать соответствующим образом при вызове компонентом. Проблема в том, что давайте предположим, что мы вызываем ensureMounted несколько раз из компонента в течение его срока службы, исходное состояние изменится — и все же ensureMounted будет действовать только на основе начального состояния.

5. @Jamiec Я обновил его, чтобы он был более четким и кратким. Я чувствую, что оригинал был слишком притуплен, чтобы иметь смысл (мне нравится делать вопросы как можно более простыми, поскольку пользователи StackOverflow, похоже, избегают всего, что длиннее нескольких строк кода).

Ответ №1:

Ваш код, похоже, работает так, как ожидалось. Очевидных проблем со ссылками нет. (ПРАВКА: По крайней мере, в коде вашего исходного вопроса).

Помните, что пользователь hook работает на «том же уровне» компонента, где он используется. Другими словами, это то же самое, как если бы вы использовали пользовательский хук useState непосредственно в своем компоненте. Действия, создающие ваш пользовательский хук «повторный рендеринг», также приведут к повторному рендерингу вашего компонента.

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

Когда ваш код запускается в первый раз, оба useEffect крючка выполняются по порядку. Первой useEffect будет выполняться только один раз, потому что он не имеет никаких зависимостей ( hook.example ссылка внутри него всегда будет первой ссылки (в конце концов), но он запускается только один раз, поэтому у вас не возникнет любой вопрос, если вы сделать некоторые другие вещи внутри этого обратного вызова (например, при добавлении прослушивателей событий или выполнения некоторых асинхронных такс) или звоните hook.example (тем самым изменяя исходное состояние) до useEffect трассы).

Продолжая, после hook.example вызова регистрируется текущее значение данных ( false ), затем запрашивается обновление состояния (асинхронно), выполнение кода продолжается, и useEffect запускается второй, регистрирующий текущее значение ( false ). Наконец, состояние обновляется, и ваш пользовательский хук «повторно визуализируется», что также приводит к повторной визуализации вашего компонента. Теперь первый useEffect не срабатывает, а второй useEffect срабатывает, потому hook.data что изменился. Наконец, регистрируется новое значение данных ( true ).

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

Пожалуйста, также обратите внимание, что хорошая практика требует, чтобы ваш первый useEffect крючок был hook в качестве зависимости (тогда логика возможной фильтрации должна выполняться внутри useEffect обратного вызова), и в вашем случае добавление hook в массив зависимостей приведет к бесконечному циклу. Возможно, ваш фактический код отличается, но это хорошо знать.

Если вам нужны дополнительные разъяснения, не стесняйтесь комментировать.

 const useHook = () => {
    const [data, setData] = React.useState(false);

    const example = () =>
        preCallCheck(() => {
            setData(!data);
        });

    const preCallCheck = (callback) => {
        console.log('preCallCheck()t', data);
        callback();
    };

    return {
        data: data,
        example: example
    };
};

const Parent = () => {
    const hook = useHook();

    React.useEffect(() => {
        hook.example();
    }, []);

    React.useEffect(() => {
        console.log('useEffect()t', hook.data);
    }, [hook.data]);

    return (
      <div>
        <h1>{'Hello, World!'}</h1>
        <button type={'button'} onClick={() => hook.example()}>{'Click Me'}</button>
      </div>
    );
};

ReactDOM.render(<Parent />, document.getElementById('root')); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>

<div id="root"></div> 

Комментарии:

1. Приношу извинения за первоначальный отказ, я случайно нажал «Принять», а затем, должно быть, случайно нажал «отклонить», когда я отказался. Просто заметил, когда вы отредактировали заново: -) К счастью, похоже, у меня все еще было время удалить его- lol

2. Спасибо за ответ, но, к сожалению, это мне не очень помогло, кроме как доказать, что мой код работает на StackOverflow, но не в моем приложении 🙁

3. @ГРОВЕР. Я увидел ваш новый опубликованный код и думаю, что ваша проблема, скорее всего, заключалась в том, что вы использовали свой собственный крючок, а не в самом крючке, который, похоже, в порядке. Вы уверены, что ваше mounted состояние в конечном итоге становится истинным?

4. Абсолютно уверен. Когда я войду _mounted в useEffect систему, он вернет и true то, и false другое, в зависимости от условий.

5. @ГРОВЕР. и если сделать console.log(state.mounted) это прямо перед вашим return заявлением внутри вашего крючка, всегда ли вы получаете желаемую ценность? Если это так, то проблема в использовании крючка.