使用 UseReducer 和 UseContext 分派后状态变为未定义

State becomes undefined after dispatch with UseReducer and UseContext

目标:我正在尝试使用 React reducer 和上下文来切换 3D 模型的可见性。 3D 渲染是使用名为 Xeokit 的框架的 React hooks 版本完成的。通过将模型作为道具提供给 Xeokit Viewer 元素 (XKTModel.js),模型被渲染到 canvas 元素 (3DCanvas.js) 中。

问题是,一旦发送了调度,状态就变得未定义了。上下文仅适用于提供给减速器的默认值。哪个组件发送调度似乎无关紧要。

我已经使用 UseContext 挂钩将每个组件的状态写入控制台以查看发生了什么。减速机也一样。

我进行了大量搜索,但找不到正在解决的类似问题。更有经验的人可以发现我犯的错误吗?

上下文创建:

import React from "react";

const ViewControlsContext = React.createContext()

export {ViewControlsContext as default}

减速器:

const ViewControlsReducer = (state, action) => {
    switch (action.type) {
      case 'POPULATE_STATE':
          return action.state, console.log("populate dispatch: ", action, state)
      case 'TOGGLE_SITE_VISIBILITY':
        return {
            ...state,
            //site_visibility: !state.site_visibility
            //^commented out, as will cause an error because state undefined
        }, console.log("toggle dispatch: ", action, state)
      default:
        return state, console.log("default: ", action, state)
    }
  }

  export {ViewControlsReducer as default}

应用程序:

import React from 'react';

import './App.scss'

import ThreeDeeCanvas from './Components/3DCanvas'

function App(){
  return (<div>
    <ThreeDeeCanvas />
  </div>)
};

export default App;

3DCanvas(渲染模型的组件,提供上下文):

import React, { useEffect, useReducer } from 'react'
import { Container, Col, Row } from 'react-bootstrap'

import XKTModel from './XKTModel';
import ThreeDeeControls from './3DControls';
import LogoCanvas from './LogoCanvas'
import ViewControlsContext from '../Context/ViewControlsContext';
import ViewControlsReducer from '../Reducers/ViewControlsReducer';

const ThreeDeeCanvas = () => {

    const def = {
        site_visibility: false
    }

    const [viewControls, dispatch] = useReducer(ViewControlsReducer, def)

    useEffect(() => {
        dispatch({type: 'POPULATE_STATE', state: def})
        dispatch({type: 'POPULATE_STATE', state: def}) //test, with this state is undefined
    }, [])
    
    console.log("3dcanvas: ", viewControls)

    return (
        <div>
            <LogoCanvas/>
            <Col className='ThreeDeeWindow'>
            <ViewControlsContext.Provider value={{viewControls, dispatch}} >
                <XKTModel/>
                <ThreeDeeControls/>
            </ViewControlsContext.Provider>
            </Col>
        </div>
    )
}

export {ThreeDeeCanvas as default}

XKTModel(3D 模型的来源和道具,使用 Context):

import React, { useContext } from 'react';
import { navCubeSettings } from '../models';
import { XKTViewer } from 'xeokit-react';

import ViewControlsContext from '../Context/ViewControlsContext';
import { IFCObjectDefaultColors } from '../default_colors';

const mainModel = {
  id: 'main_model',
  src: './xkt/Showcase.xkt',
  metaModelSrc: './xkt/Showcase.json',
  edges: true,
  objectDefaults: IFCObjectDefaultColors,
}

const siteModel = {
  id: 'site_model',
  src: './xkt/ShowcaseSite.xkt',
  metaModelSrc: './xkt/ShowcaseSite.json',
  edges: true
}

const XKTModel = () => {

  const {viewControls} = useContext(ViewControlsContext)
  console.log("XKTModel: ", viewControls)

  //siteModel.visible = viewControls.site_visibility
  //^commented out, as will cause an error because state undefined
  
 
  return (
    <XKTViewer
    canvasID="modelCanvas"
    width={800}
    height={800}
    models={[mainModel, siteModel]}
    navCubeSettings={navCubeSettings}
    />
  ) 
};

export default XKTModel;

3DControls(独立 3D 控件的容器):

import React, { useContext } from 'react'
import { Container, Col, Row } from 'react-bootstrap'

import ThreeDeeControl from './3DControl'
import ViewControlsContext from '../Context/ViewControlsContext'

const ThreeDeeControls = () => {

    const {viewControls} = useContext(ViewControlsContext)
    
    console.log("3dcontrols: ", viewControls)

    return (
        <Container className='ThreeDeeControls p-0 d-flex justify-content-between'>
            <ThreeDeeControl id='site' label='Show site'/>
        </Container>
    )
}

export {ThreeDeeControls as default}

3DControl(用于更改 3D 视图道具的单独切换,发送调度):

import React, {  useState, useContext, useReducer } from 'react'
import { Container, Form } from 'react-bootstrap'

import ViewControlsContext from '../Context/ViewControlsContext'


const ThreeDeeControl = ({id, label}) => {

    const {viewControls, dispatch} = useContext(ViewControlsContext)

    console.log("3dsinglecontrol: ", viewControls)

    const [isSwitchOn, setIsSwitchOn] = useState(false);

    const onSwitchAction = () => {
    setIsSwitchOn(!isSwitchOn);
    dispatch({type: 'TOGGLE_SITE_VISIBILITY'})
    }

    return (
            <Form className='ThreeDeeControl'>
                <Form.Check  
                    type="switch"
                    id={id}
                    label={label}
                    checked={isSwitchOn}
                    onChange={onSwitchAction}
                />
            </Form>
    )
}

export {ThreeDeeControl as default}

您正在 return 逗号运算符的结果,即 console.log 的结果,它是一个空值 return。

Comma Operator

The comma operator (,) evaluates each of its operands (from left to right) and returns the value of the last operand. This lets you create a compound expression in which multiple expressions are evaluated, with the compound expression's final value being the value of the rightmost of its member expressions.

解决方案

记录独立于 reducer 函数的状态和操作 return。

const ViewControlsReducer = (state, action) => {
  switch (action.type) {
    case 'POPULATE_STATE':
      console.log("populate dispatch: ", { action, state });
      return action.state;

    case 'TOGGLE_SITE_VISIBILITY':
      console.log("toggle dispatch: ", { action, state });
      return {
        ...state,
        site_visibility: !state.site_visibility
      };

    default:
      console.log("default: ", { action, state });
      return state;
  }
}