【发布时间】:2020-07-04 09:14:38
【问题描述】:
我有一个 Ul 组件,其中包含一组具有不同值的对象。此列表中名为 TestCase 的每个项目都有一个按钮,该按钮进行休息调用并更新其对象。然而,并不是所有的 TestItems 都需要更新。只有那些按钮被点击的人。该数组的状态存储在称为 TestCaseContainer 的父容器组件中。但是,我的按钮将根据数组中受影响的 TestItem 相应地更新状态。这会导致整个列表重新呈现。我怎样才能只渲染更改后的 TestItems,而不是每次更新元素时都渲染整个 ul。我阅读了有关使用 useMemo 以便组件可以记住传递下来的道具的信息,但是我不知道如何正确实现这一点。
我怎样才能停止所有的重新渲染!为乱码提前道歉!
这是我的代码
Regression.js - 保存所有状态
const Testing = forwardRef((props,ref) => {
const templateTestItem = {id:0,formData:{date:'',env:'',assetClass:'',metric:'',nodeLevel:'',nodeName:'',testName:'',dataType:'',tradeId:''},results:[],isLoading:false}
const testCaseRef = useRef()
const [isRun, setIsRun] = useState(false)
const [testItems, setTestItems] = useState([ templateTestItem])
const [stats,setStats] = useState(null)
const addTestItem = () => {
const newIndex = testItems.length
// console.log(newIndex)
const templateTestItem = {id:newIndex,formData:{date:'',env:'',assetClass:'',metric:'',nodeLevel:'',nodeName:'',testName:'',dataType:'',tradeId:''},results:[],isLoading:false}
setTestItems([...testItems, templateTestItem])
}
const addUploadCases = (cases) => {
setTestItems([])
const UploadedItems = cases.map((item,index)=>{
return{
id:index,
formData:{
date:item['date'],
env:item['env'],
assetClass:item['asset_class'],
metric:item['metric'],
nodeLevel:item['node_level'],
nodeName:item['node_name'],
testName:item['test_name'],
dataType:item['dataType'],
tradeId:item['tradeId']
},
results:[]
}
})
setTestItems(UploadedItems)
}
const runAllTests = () => {
testCaseRef.current.runAll()
}
const clearTestCases = () => {
// console.log('Clear Test cases')
setTestItems([])
if (testItems.length == 0) {
setTestItems([templateTestItem])
}
}
const extractAllResults =()=>{
testCaseRef.current.extractAllResults()
}
const updateTestResults = useCallback( (result, index) => {
console.log('Index:', index)
setTestItems(prevObjs=>(prevObjs.map((item)=>{
let updatedItem = { ...item, results: result }
if(item.id==index) return updatedItem
return item
})))
},[])
return (
<div style={{ 'backgroundColor': '#1b2829', 'display': 'flex', }} className={styles.dashboard}>
<Grid>
<Row stretched style={{}} className={styles.buttonConsole}>
{<ButtonConsole addTest={addTestItem} addUploadCases={addUploadCases} runAllTests={runAllTests} clearTestCases={clearTestCases} extractAllResults={extractAllResults} />}
</Row>
<Row centered>
<TestRunStats stats={stats}/>
</Row>
<Row style={{ 'display': 'flex', 'flex-direction': 'column' }} ><TestCaseContainer countTestRunStats={countTestRunStats} updateTestResults={updateTestResults} isRun={isRun} ref={testCaseRef} testItems={testItems} /> </Row>
{/*
<Row></Row>
<Row></Row> */}
</Grid>
</div>
);
})
TestContainer.js
const TestCaseContainer = forwardRef((props, ref) => {
const testCaseRef = useRef([])
useImperativeHandle(ref, () => ({
extractAllResults: async () => {
const data = {
data:[],
summary:[]
}
testCaseRef.current.forEach(async (item, index) => {
try {
const workbook = item.extractAllResults()
const summary = workbook['summary']
workbook['data'].forEach(testData => {
data['data'].push(testData)
})
data['summary'].push(summary)
} catch (err) {
console.log(err)
}
})
await axios.post('http://localhost:9999/api/downloadresults', data).then(res => {
console.log('res', res)
const byteCharacters = atob(res.data);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
const blob = new Blob([byteArray], { type: 'application/vnd.ms-excel' });
saveAs(blob, 'TestResults.xlsx')
})
},
runAll: () => {
testCaseRef.current.forEach(async (item, index) => {
await item.runAll()
})
}
}));
const runTestCase = async (date, env, nodeLevel, nodeName, assetClass, metric, dataType, tradeId, testName, key) => {
let testKey = key
console.log('FEtCHING', testKey)
try {
const params = {
nodeName,
date,
env,
nodeLevel,
assetClass,
metric,
dataType,
tradeId,
testName
}
const endpoint ={
sensitivities:'sensitivities'
}
if (metric == 'DELTA_SENSITIVITIES') {
const result = await axios.get('http://wind08132495.tdbfg.com:9999/api/sensitivities', { params, }).then(response => {
console.log('response.data', response.data)
return response.data
})
if (result.data == 'none') {
toast.error(`${date}-${metric}-${nodeName} failed queried! No valutations for trades`, {
autoClose: 8000,
position: toast.POSITION.TOP_RIGHT
});
} else if (result.data != 'none') {
// setTestResult(result)
props.updateTestResults(result, testKey)
// updateTestResults(false,testKey,'isLoading')
toast.success(`${date}-${metric}-${nodeName} Successfully queried!`, {
autoClose: 8000,
position: toast.POSITION.TOP_RIGHT
});
}
// setTestResult(result.data)
} else {
await axios.get(`http://localhost:9999/api/metric/${metric}`, { params, }).then(response => {
if (response.data != 'none') {
props.updateTestResults(response.data, testKey)
toast.success(`${date}-${metric}-${nodeName} Successfully queried!`, {
autoClose: 8000,
position: toast.POSITION.TOP_RIGHT
});
} else {
toast.error(`${date}-${metric}-${nodeName} failed queried! No valutations for trades`, {
autoClose: 8000,
position: toast.POSITION.TOP_RIGHT
});
}
})
}
} catch (error) {
toast.error(`${date}-${metric}-${nodeName} failed queried! -${error
}`, {
autoClose: 8000,
position: toast.POSITION.TOP_RIGHT
});
}
}
return (
<Segment style={{ 'display': 'flex', 'width': 'auto', 'height': '100vh' }} className={styles.testCaseContainer}>
<div style={{ 'display': 'flex', }}>
</div>
<ul style={{overflowY:'auto',height:'100%'}} className='testItemContainer'>
{
// memoTestTwo
// testList
props.testItems.map((item, index) => {
let testName
if (item['formData']['testName'] == '') {
testName = `testRun-${index}`
} else {
testName = item['formData']['testName']
}
return <TestCase testResult={item['results']} runTestCase={runTestCase} isRun={props.isRun} ref={el => (testCaseRef.current[index] = el)} testKey={index} key={index} date={item['formData']['date']} env={item['formData']['env']} assetClass={item['formData']['assetClass']} metric={item['formData']['metric']} nodeLevel={item['formData']['nodeLevel']} nodeName={item['formData']['nodeName']} testName={testName} dataType={item['formData']['dataType']} tradeId={item['formData']['tradeId']} hierarchy={hierarchy} />
})
}
</ul>
</Segment>
)
})
TestCase.js - 从映射渲染的单个项目!
const TestCase = forwardRef((props, ref) => {
const [isLoading, setIsLoading] = useState(false)
const inputRefs = useRef()
const outputRefs = useRef()
useImperativeHandle(ref, () => ({
extractAllResults: () => {
return outputRefs.current.extractAllResults();
},
runAll: () => {
inputRefs.current.runAll()
},
}));
const runSingleTestCase = async (date, env, nodeLevel, nodeName, assetClass, metric, dataType, tradeId, testName, key) => {
setIsLoading(true)
await props.runTestCase(date, env, nodeLevel, nodeName, assetClass, metric, dataType, tradeId, testName, key)
setIsLoading(false)
}
const convertDate = (date) => {
if (date) {
const newDate = date.split('/')[2] + '-' + date.split('/')[0] + '-' + date.split('/')[1]
return newDate
} else {
return date
}
}
return (
<Segment color='green' style={{ 'display': 'flex', 'flexDirection': 'column', }}>
<div style={{ 'display': 'flex', 'justify-content': 'space-between' }}>
<div style={{ 'display': 'flex', 'height': '30px' }}>
<Button
// onClick={props.deleteSingleTest(props.testKey)}
icon="close"
inverted
size="tiny"
color='red'
></Button>
</div>
<RegressionInput runSingleTestCase={runSingleTestCase} isRun={props.isRun} testKey={props.testKey} ref={inputRefs} nodeNames={props.hierarchy} runTestCase={props.runTestCase} date={convertDate(props.date)} testName={props.testName} env={props.env} assetClass={props.assetClass} metric={props.metric} nodeLevel={props.nodeLevel} nodeName={props.nodeName} dataType={props.dataType} tradeId={props.tradeId} />
<TestCheck pass={props.testResult ? props.testResult['CHECK'] : null} />
</div>
{
isLoading ? (<Loading type={'circle'} style={{ 'display': 'flex', 'flexDirecton': 'column', 'justify-content': 'center', 'align-items': 'center', 'marginTop': '50' }} inline />) : (
<RegressionOutput ref={outputRefs} testName={props.testName} testResult={props.testResult} />
)
}
</Segment>
)
})
【问题讨论】:
-
您正在映射项目,因此每当您的数组更改时,所有项目都会重新映射 - 这是无法避免的。但是,您使用索引作为项目键
key={index}。请阅读documentation on using keys,特别是this on how keys work。基本上,当您添加或删除一个项目时,React 认为大多数/所有项目都已更改,因为您使用索引作为键。为防止这种情况发生,请使用特定于项目的键,例如测试用例 ID。 -
我在我的对象中添加了一个键,它被传递给我的
组件,所以它现在就像这样 key={item['id']},但它似乎并没有停止重新渲染整个数组 -
重新渲染是什么意思?你怎么知道整个数组正在重新渲染?
-
我在 TestItem 组件内部有一个很长的控制台。我可以看到控制台中出现了不变的项目
-
我在之前的评论中说过“您正在映射项目,因此每当您的数组更改时,所有项目都会重新映射 - 这是无法避免的。”这意味着所有项目都将运行它们的渲染周期。如果渲染中有控制台日志,它将记录到控制台。这并不意味着实际的 DOM 元素会被重新渲染。如果父状态发生更改,或者子组件的道具发生更改,或者在您的情况下,如果您将数据映射到组件并且数组发生更改,则所有子组件都将被重新评估。这没有什么问题。
标签: javascript reactjs react-hooks render rerender