React子组件隐藏状态不更新:如何让RtSelect正常显示?
选择项目后,作为子组件的RtSelect隐藏状态未更新,本该显示所选项目的所有任务。getTasks异步请求完成后,已将hidden设为false并传给子组件props,但RtSelect始终不重新渲染,保持隐藏状态。需要修改哪些代码让它响应hidden变化?
父组件代码
import React, { useState, useEffect, useRef } from 'react'; import RtSelect from './RtSelect'; import api, { route } from "@forge/api"; function Projects() { const projRef = useRef(); const taskRef = useRef(); const [projects, setProjects] = useState(undefined) const [tasks, setTasks] = useState(undefined) const [projectid, setProjectid] = useState(undefined) const [taskid, setTaskid] = useState(undefined) const [hidden, setHidden] = useState(true) // 从Atlassian获取项目数组 useEffect(() => { let loadedProject = true; const fetchProjects = async () => { const response = await api.asUser().requestJira(route`/rest/api/3/project`, { headers: { 'Accept': 'application/json' } }); const data = await response.json(); const result = data.map(function (item) { console.log('test'); return [ { label: item.name, value: item.id, avatar: item.avatarUrls['16x16'] } ] }) if (loadedProject) { setProjects(result); } } fetchProjects() .catch(console.error); return () => loadedProject = false; }, [param]) const getTasks = async (p) => { const response = await api.asUser().requestJira(route`/rest/api/3/issuetype/project?projectId={p}`, { headers: { 'Accept': 'application/json' } }); const data = await response.json(); const result = data.map(function (item) { console.log('test'); return [ { value: item.id, label: item.description, avatar: item.iconUrl } ] }) setTasks(result) setHidden(false) } useEffect(() => { projRef.current.addEventListener("onChange", (e) => { setProjectid(e.target.value) console.log("项目选择框的值为: " + e.target.value); getTasks(projectid) }); }); useEffect(() => { taskRef.current.addEventListener("onChange", (e) => { setTaskid(e.target.value) console.log("任务选择框的值为: " + e.target.value); }); }); return ( <div> <div className='projects'> <RtSelect info="选择项目:" options={projects} hidden={false} ref={projRef} /> </div> <div className='tasks'> <RtSelect info="选择任务:" options={tasks} hidden={hidden} ref={taskRef} /> </div> </div> ); } export default Projects
RtSelect子组件代码
import React from "react"; import Select from "react-select"; class RtSelect extends React.Component { state = { info: this.props.info, options: this.props.options, hidden: this.props.hidden, menuIsOpen: '', menuWidth: "", IsCalculatingWidth: '' }; constructor(props) { super(props); this.selectRef = props.ref this.onMenuOpen = this.onMenuOpen.bind(this); this.setData = this.setData.bind(this); } componentDidMount() { if (!this.state.menuWidth && !this.state.isCalculatingWidth) { setTimeout(() => { this.setState({IsCalculatingWidth: true}); this.selectRef.current.select.openMenu(); this.setState({menuIsOpen: true}); }, 1); } } onMenuOpen() { if (!this.state.menuWidth && this.state.IsCalculatingWidth) { setTimeout(() => { const width = this.selectRef.current.select.menuListRef.getBoundingClientRect() .width; this.setState({menuWidth: width}); this.setState({IsCalculatingWidth: false}); this.selectRef.current.select.onMenuClose(); this.setState({menuIsOpen: undefined}); }, 1); } } styles = { menu: (css) => ({ ...css, width: "auto", ...(this.state.IsCalculatingWidth && { height: 0, visibility: "hidden" }) }), control: (css) => ({ ...css, display: "inline-flex " }), valueContainer: (css) => ({ ...css, ...(this.state.menuWidth && { width: this.state.menuWidth }) }) }; setData (props) { if (props.info) { this.setState({ info: props.info }) } if (props.options) { this.setState({ options: props.options }) } if (props.hidden) { this.setState({ hidden: props.hidden }) } } render () { return ( <div style={{ display: "flex" }}> <div style={{ margin: "8px" }}>{this.state.info}</div> <div style={{minWidth: "200px"}}> <Select ref={this.selectRef} onMenuOpen={this.onMenuOpen} options={this.state.options} menuIsOpen={this.state.menuIsOpen} styles={this.styles} isDisabled={this.state.hidden} formatOptionLabel={(options) => ( <div className="select-option" style={{ display: "flex", menuWidth: "200px"}}> <div style={{ display: "inline", verticalAlign: "center" }}> <img src={options.avatar} width="30px" alt="Avatar" /> </div> <div style={{ display: "inline", marginLeft: "10px" }}> <span>{options.label}</span> </div> </div> )} /> </div> </div> ); } } export default RtSelect;
必须修改的代码点
1. 让RtSelect响应props变化
你把父组件传入的hidden、options等props存在了子组件自己的state里,但只在初始化时赋值,props更新时不会同步到state。有两种修复方式:
方式一:直接使用props(推荐)
删掉子组件state中info、options、hidden的初始化,所有用到这些值的地方直接用this.props替代:
// 移除state中的对应字段 state = { menuIsOpen: '', menuWidth: "", IsCalculatingWidth: '' }; // render里修改对应部分 <div style={{ margin: "8px" }}>{this.props.info}</div> // ... <Select // ... options={this.props.options} isDisabled={this.props.hidden} // ... />
方式二:添加生命周期同步props到state
如果一定要保留state,给RtSelect添加componentDidUpdate方法:
componentDidUpdate(prevProps) { if (prevProps.hidden !== this.props.hidden) { this.setState({ hidden: this.props.hidden }); } if (prevProps.options !== this.props.options) { this.setState({ options: this.props.options }); } if (prevProps.info !== this.props.info) { this.setState({ info: this.props.info }); } }
2. 修复getTasks的参数错误
父组件中,setProjectid是异步更新,你调用getTasks(projectid)时,projectid还没更新成新值,应该直接传当前选中的id:
projRef.current.addEventListener("onChange", (e) => { const selectedId = e.target.value; setProjectid(selectedId); console.log("项目选择框的值为: " + selectedId); getTasks(selectedId); // 直接传选中值,而非state里的projectid });
3. 给useEffect添加依赖数组
两个绑定事件的useEffect没有依赖数组,会导致每次渲染都重复绑定事件,引发冗余逻辑:
useEffect(() => { // 项目选择框的事件绑定代码 }, []); // 添加空数组,只绑定一次 useEffect(() => { // 任务选择框的事件绑定代码 }, []); // 添加空数组,只绑定一次
4. 修复ref传递错误
RtSelect中直接把props.ref赋值给内部的selectRef,会导致父组件的ref失效,应该用React.forwardRef正确传递:
// 修改RtSelect的导出方式 const RtSelect = React.forwardRef((props, ref) => { // 原组件代码,把this.selectRef换成ref即可 // ... }); export default RtSelect;
内容的提问来源于stack exchange,提问作者Jeroi
相关产品推荐
相关产品推荐

