React redux, Uncaught TypeError: Cannot assign to read only property 'current' of object '#<Object>'

React redux, Uncaught TypeError: Cannot assign to read only property 'current' of object '#<Object>'

我正在制作一个修改数据库数据的网站。 一、组件结构如下

<Contents />
 <Table />
  <Row />
   <Column />
    <Input />

创建行组件时,创建输入组件的ref并由redux管理。

const StyledRow = styled.div`
    text-align:center;
    display:flex;
    align-items:center;
`;


const DeleteButton = styled(Button)`
    background-color: #ff7787;
    margin-right:5px;
    color:white;
    width:40px;
    
    ${({top}) => top && css`
        background-color:white;
        color:white;
        width:40px;
    `}
`;

function Row({top, rowId}){

    const dispatch = useDispatch();
    const columns = useMemo(() => columnPhoneInfo,[]);

    const inputsRef = useMemo(()=>
        !top && 
            Array(8).fill(0).map(() => createRef()
    ),[]);
    // const inputsRef = useRef([]);

    useEffect(()=> {
        // console.log(rowId,top);
        !top &&
            dispatch(phoneDataAddRef(rowId,inputsRef));
    },[]);

    const handleDeleteButton = useCallback( (id) => {
            dispatch(phoneDataUpdate.Delete(id));
    },[]);

    if( top ) return(
        <StyledRow>
            <DeleteButton top/>
            {columns.map((column)=>
                <Column  key={`head_${column.name}`} width={column.width} top>
                    {column.name}
                </Column>
            )}
        </StyledRow>

    );
    return( 
        <StyledRow>
            <DeleteButton onClick={()=>handleDeleteButton(rowId)}> delete </DeleteButton>
            {columns.map((column, index)=>
                <Column key={`row_${rowId}_${column.name}`} width={column.width} textalign={column.textalign}>
                    <Input  ref={inputsRef[index] } colIndex={index} id={rowId} column={column} />
                    {/* <Input colIndex={index} id={rowId} column={column} /> */}
                </Column>   
            )}
        </StyledRow>
    );
}
export default React.memo(Row);

输入组件只接收 ref 作为 forwardRef

const StyledInput = styled.input`
    ${({ width, textalign })=>css`
        width:${width};
        text-align:${textalign};
    `}
`;


const Input = forwardRef(({colIndex, id},inputRef) =>{
    const dispatch = useDispatch();
    const didShowAlert = useRef(false);

    const nowColumnInfo = columnPhoneInfo[colIndex];
    const nowColumnValidCheck = inputValidCheck[colIndex];
    
    const { nowVal, firstVal, isAddedRow } = useSelector(state =>({
        nowVal     : state.phoneData.data.rows.find(val=>val.id === id)[nowColumnInfo.colname],
        firstVal   : state.phoneData.firstData.lastId < id 
                     ? null
                     : state.phoneData.firstData.rows.find(val=>val.id===id)[nowColumnInfo.colname],
        isAddedRow : state.phoneData.firstData.lastId < id
                     ? true
                     : false,
    }),shallowEqual);

    const callbackDispatch = useCallback((dispatchFunc) =>{
        return(...args)=>{
            dispatch(dispatchFunc(...args));
        }
    },[dispatch]);

    //////////////////////
    const inputChange = useCallback( (value) => 
        dispatch(phoneDataUpdate.Change(id,nowColumnInfo.colname, value))
    ,[nowColumnInfo.colname, dispatch, id]);
    
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    const updateListChange = callbackDispatch(phoneDataUpdateList.Change);
    const updateListDelete = callbackDispatch(phoneDataUpdateList.Delete);
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    const handleChange = useCallback( (e) => {
        // ... todo handle change
    },[]);

     ///////////////////////////////////////////////////////// 
    const handleBlur = useCallback( (e) =>{
        // ... todo handle blur
    },[]);

    return( 
        <StyledInput 
            textalign={nowColumnInfo.textalign} 
            width={nowColumnInfo.width} 
            value={nowVal === null ? '': nowVal }
            onChange={handleChange}
            onBlur={handleBlur}
            ref={inputRef}
            // placeholder={}
        />
    );
});
export default React.memo(Input);

最后,redux 模块

////////////////////////////////////////////////////////
const PHONE_DATA_DELETE = 'phoneData/PHONE_DATA_DELETE';
////////////////////////////////////////////////////////
const PHONE_DATA_ADD_REF = 'phoneData/PHONE_DATA_ADD_REF';
////////////////////////////////////////////////////////

const dataInitRow = {
    id:null,
    model_name:null,
    machine_name:null,
    shipping_price:null,
    maker:null,
    created:null,
    battery:null,
    screen_size:null,
    storage:null,
};

const dataInit = {
    lastId:null,
    rows:[],
}

const initialState = {
    state:{
        loading:false,
        error:false,
    },
    data:dataInit,
    refData:[],
    firstData:dataInit,
    dataChangeList:{
        dataAddList:[],
        dataDeleteList:[],
        dataUpdateList:[],
    },
};



const phoneDataFetchAsync = createPromiseThunk(PHONE_DATA, restAPI.getAllPhoneInfo);
////////////////////////////////////////////////////////
const phoneDataAddRef=(id, ref) =>({
    type:PHONE_DATA_ADD_REF,
    id:id,
    ref:ref,
});

const phoneDataUpdateList = ({
    Change:(id,colName, value) => ({
        type:PHONE_DATA_UPDATE_LIST_CHANGE,
        id: id,
        colName: colName,
        value: value,
    }),
    Delete:(id, colName) => ({
        type:PHONE_DATA_UPDATE_LIST_DELETE,
        id: id,
    }),
});

////////////////////////////////////////////////////////

export default function phoneData(state = initialState, action){
    // console.log(`add: ${state.dataChangeList.dataAddList}, delete: ${state.dataChangeList.dataDeleteList}, change: ${state.dataChangeList.dataUpdateList}`);
    switch(action.type)
        case PHONE_DATA_DELETE:
            return produce(state, draft=>{
                console.log(action);

                const idx = state.dataChangeList.dataAddList.findIndex( val => val === action.id);
                if(  idx === -1 )
                    draft.dataChangeList.dataDeleteList.push(action.id);
                else
                    draft.dataChangeList.dataAddList.splice(idx,1);


                draft.refData = state.refData.filter(row => row.id !== action.id);
                draft.data.rows = state.data.rows.filter(row =>row.id !== action.id);
            });
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////
        case PHONE_DATA_ADD_REF:
            return produce(state, draft=>{
                draft.refData.push({id:action.id, refs:action.ref});     
            });
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////
        default:
            return state;
    }
}




export {phoneDataFetchAsync,
        phoneDataDelete,,
        phoneDataAddRef,
        };

问题区域是删除按钮。当我按下按钮时,出现该错误。 但是,如果不将 ref 添加到状态,则不会发生错误。或者即使我注释掉底部,也没有错误。

draft.data.rows = state.data.rows.filter(row =>row.id !== action.id);

或者把下面的部分注释掉

draft.refData.push({id:action.id, refs:action.ref});

我今天一整天都在尝试修复它,但我不知道哪里出了问题。我该如何解决?

不要在 Redux 中存储引用

您在这里尝试做的事情不仅违反了“基本原则”中的一项,还违反了两项rules of Redux

  1. Mutation of state

Ref 对象在设计上是可变的。通过更新 ref 对象的 .current 属性 来更改引用,但对象实例保持不变。这违反了状态必须不可变的 Redux 规则,因为 Redux 状态可以被不相关的 React 代码改变。

  1. Non-Serializable

Redux 存储中的所有数据都应该能够转换为 JSON string 并返回。任何在转换中丢失的东西都不属于 Redux。 DOM 元素的实例不可序列化。

您需要弄清楚需要哪些原始数据来表示您的状态。将该数据存储在 Redux 中并使用它来生成正确的 DOM 元素。在 Redux 中存储 DOM 是倒退的。