请问,react中页面渲染完成的是哪个生命周期呢?

请问,react中渲染完成的是哪个生命周期呢?
我认为是:componentDidMount

  useEffect(() => {
    console.log('mounted') // 挂载后
  }, [])

可以当我在这个生命周期内,document.querySelector查询id的时候:却报错

    useEffect(() => {
        if(props.scrollToIdx) {

            const scrollToIdx = props.scrollToIdx 
            
            console.log("滚动ID:", '#item-' + scrollToIdx!.toString())

            const container = document.querySelector("#container")
            const div: HTMLDivElement | null = document.querySelector('#item-' + scrollToIdx!.toString()) // 这里查询出是null
        
            console.log(container, div)
            if(container) {
                container.scrollTo({
                    top: div!.offsetTop,
                    behavior: 'smooth'
                });
            }
        }

    }, [])

报错信息:

Cannot read properties of null (reading 'offsetTop')
TypeError: Cannot read properties of null (reading 'offsetTop')
    at 

也就是说,没有查询出#item-20,原因是还没有渲染到DOM。

请问这个应该如何进行避免呢?

===

完整代码如下:
subContainer.tsx


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

export type ItemType = {
    type: "property" | "method",
    value: string,
    selected?: boolean    
}

export type SubContainerProps = {
    height?: number,
    title: string,
    data: ItemType[], // 这个是items
    scrollToIdx?: number // 滚动到具体的index的data数组元素
    selected?: boolean // class是否被选中
    
}

export default function SubContainer (props: SubContainerProps){ 

    const [data, setData] = useState<ItemType[]>([])

    // 滚动
    useEffect(() => {
        if(props.scrollToIdx) {

            const scrollToIdx = props.scrollToIdx 
            
            console.log("滚动ID:", '#item-' + scrollToIdx!.toString())

            scrollTo(scrollToIdx)
        }
    }, [props.scrollToIdx])

    const scrollTo = (scrollToIdx: number) => {
        const container = document.querySelector("#container")
        const div: HTMLDivElement | null = document.querySelector('#item-' + scrollToIdx!.toString())
    
        console.log(container, div)
        if(container) {
            container.scrollTo({
                top: div!.offsetTop,
                behavior: 'smooth'
            });
        }
    }


    useEffect(() => {
        setData(props.data)
    }, [props.data])

    

    const itemClick = (item: ItemType, index: number) => {
        console.log(item, index)
    }

    const searchChange = (e: ChangeEvent<HTMLInputElement>) => {
        //console.log(e.target.value)
        const searchValue = e.target.value;
        const filteredData = props.data.filter(item => {
            // 假设您要匹配 item 的某个属性,比如 value
            return item.value.toLowerCase().includes(searchValue.toLowerCase());
        });
        // 在这里根据 filteredData 进行后续的处理,比如更新组件状态或重新渲染相关部分
        console.log(filteredData);

        setData(filteredData)
    }



    return (
        <div style={{
            borderRadius: '8px',  
            border: '2px dashed #333', 
            height: props.height ? props.height : ''
            }}>
            <div style={{
                textAlign: 'left',
                height: '40px',
                display: 'flex',
                alignItems: 'center'
                }}>
                <label style={{
                    float: 'left',
                    color: props.selected ? 'green' : '',
                    fontWeight: props.selected ? 'bold' : ''
                    }}>{props.title}
                </label>
                <input 
                style={{float: 'right', marginLeft: 'auto'}} 
                placeholder='搜索:'
                onChange={searchChange}
                ></input>
            </div>
            <div
                id="container"
                style={{
                    overflow: 'auto',
                    height: props.height? props.height - 40 : ''
                }}  
                >
                {data.map((item, index) => (
                    <div 
                        id={ `item-${index}` }
                        key={`${index}-${item.type}-${item.value} `} 
                        style={{ 
                            float: 'left', 
                            display: 'inline-block', 
                            borderRadius: '6px', 
                            border: '2px solid #000', 
                            margin: '8px', 
                            padding:'4px',
                            backgroundColor: item.selected ? '#39fe40': '' 
                        }}
                        onClick={() => {
                            itemClick(item, index)
                        }}
                    >{item.value}</div>
                ))}
            </div>
        </div>
    )
}

调用:

// 创建的props
 
  //滚动位置
  const [selIdx, setSelIdx] = useState<number>(0)
  
  const props:SubContainerProps = {
    data: [
      { type: 'property', value: 'firstName' },
      { type: 'property', value: 'lastName' },
      { type: 'property', value: 'firstName' },
      { type: 'property', value: 'lastName' },
      { type: 'property', value: 'firstName' },
      { type: 'property', value: 'lastName' },
      { type: 'property', value: 'firstName' },
      { type: 'property', value: 'lastName' },
      { type: 'property', value: 'firstName' },
      { type: 'property', value: 'lastName' },
      { type: 'property', value: 'firstName' },
      { type: 'property', value: 'lastName' },
      { type: 'property', value: 'firstName' },
      { type: 'property', value: 'lastName' },
      { type: 'property', value: 'firstName' },
      { type: 'property', value: 'lastName' },
      { type: 'property', value: 'firstName' },
      { type: 'property', value: 'lastName' },
      { type: 'property', value: 'firstName' },
      { type: 'property', value: 'lastName' },
      { type: 'method', value: 'getFullname', selected: true },
    ],
    height: 120,
    title: 'classA',
    selected: true,
    scrollToIdx: selIdx
  }

<SubContainer {...props}></SubContainer>
阅读 2.4k
5 个回答

问题出在这行代码上

useEffect(() => {
  setData(props.data)
}, [props.data])

data 是通过 useEffect 写入的,当第一次 render 的时候 data 还是空,你可以试着把这部分代码放到最前面,就会报错 Cannot read properties of undefined (reading 'map')。
解法也很简单:

const [data, setData] = useState<ItemType[]>(props.data);

PS:从提供的代码来看,建议移除 data 这个 state,完全多余。

是不是#item-20异步从接口中获取的,如果是非同步获取的话,在页面首次完成渲染的时候useEffect(,[]),获取不到#item-20是符合预期的

根据后续补充已回复在评论里

新手上路,请多包涵

react 更新 Dom 是一个异步的过程,如果需要在更新数据后同步更新 Dom,请将将你的更新逻辑代码写在 flushSync API 里面

直接拿取props.data使用即可,不要设置state,不过也可以给你一个检测dom是否加载完成的方法:

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

function MyComponent() {
  const myRef = useRef(null);

  useEffect(() => {
    // 确保 DOM 已经完成渲染
    const checkDom = () => {
      if (myRef.current) {
        // DOM 元素已存在
        console.log('DOM element is ready:', myRef.current);
        // 执行 DOM 操作
      } else {
        // DOM 元素尚未准备好
        console.log('DOM element is not ready yet.');
      }
    };

    // 在下一个微任务中检查 DOM 是否准备好
    Promise.resolve().then(checkDom);

    // 清理副作用(如果有需要)
    return () => {
      // 清除任何需要清理的操作
    };
  }, []); // 依赖数组为空

  return (
    <div>
      <h1 ref={myRef}>Hello, world!</h1>
    </div>
  );
}

export default MyComponent;
推荐问题
logo
Microsoft
子站问答
访问
宣传栏