我有一个React组件,在组件的渲染方法中,我有这样的东西:
render() {
return (
<div>
<div>
// removed for brevity
</div>
{ switch(...) {} }
<div>
// removed for brevity
</div>
</div>
);
}
Now the point is that I have two div elements, one at the top and one at the bottom, that are fixed. In the middle I want to have a switch statement, and according to a value in my state I want to render a different component. So basically, I want the two div elements to be fixed always, and just in the middle to render a different component each time. I'm using this to implement a multi-step payment procedure). Though, as is the code currently it doesn't work, as it gives me an error saying that switch is unexpected. Any ideas how to achieve what I want?
Lenkan的回答是一个很好的解决方案。
<div>
{{ beep: <div>Beep</div>,
boop: <div>Boop</div>
}[greeting]}
</div>
如果需要一个默认值,那么您甚至可以这样做
<div>
{{ beep: <div>Beep</div>,
boop: <div>Boop</div>
}[greeting] || <div>Hello world</div>}
</div>
或者,如果这对你来说不太好,那么你可以做一些
<div>
{
rswitch(greeting, {
beep: <div>Beep</div>,
boop: <div>Boop</div>,
default: <div>Hello world</div>
})
}
</div>
with
function rswitch (param, cases) {
if (cases[param]) {
return cases[param]
} else {
return cases.default
}
}
你不能在渲染中有开关。放置访问一个元素的对象文字的伪切换方法并不理想,因为它会导致所有视图都要处理,并且可能导致在该状态下不存在的道具的依赖错误。
这里有一个很好的干净的方法来做到这一点,不需要每个视图提前渲染:
render () {
const viewState = this.getViewState();
return (
<div>
{viewState === ViewState.NO_RESULTS && this.renderNoResults()}
{viewState === ViewState.LIST_RESULTS && this.renderResults()}
{viewState === ViewState.SUCCESS_DONE && this.renderCompleted()}
</div>
)
如果视图状态的条件不只基于一个简单的属性——比如每行有多个条件,那么枚举和getViewState函数封装条件是分离条件逻辑和清理呈现的好方法。
我不太喜欢当前的任何答案,因为它们要么太啰嗦,要么需要您在代码中跳跃才能理解发生了什么。
我更喜欢用一个更以react组件为中心的方式来做这件事,通过创建一个<Switch/>。这个组件的任务是获取一个道具,并且只呈现子道具与该道具匹配的子元素。所以在下面的例子中,我在开关上创建了一个测试道具,并将其与子节点上的值道具进行比较,只渲染匹配的值道具。
例子:
const Switch = props => {
const { test, children } = props
// filter out only children with a matching prop
return children.find(child => {
return child.props.value === test
})
}
const Sample = props => {
const someTest = true
return (
<Switch test={someTest}>
<div value={false}>Will display if someTest is false</div>
<div value={true}>Will display if someTest is true</div>
</Switch>
)
}
ReactDOM.render(
<Sample/>,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="react"></div>
您可以根据自己的需要进行简单或复杂的切换。不要忘记对子节点及其值道具执行更健壮的检查。
这个答案专门用来解决@tonyfat提出的“重复”问题,关于如何使用条件表达式来处理相同的任务。
Avoiding statements here seems like more trouble than it's worth, but this script does the job as the snippet demonstrates:
// Runs tests
let id = 0, flag = 0;
renderByFlag(id, flag); // jobId out of range
id = 1; // jobId in range
while(++flag < 5){ // active flag ranges from 1 to 4
renderByFlag(id, flag);
}
// Defines a function that chooses what to render based on two provided values
function renderByFlag(jobId, activeFlag){
jobId === 1 ? (
activeFlag === 1
? render("A (flag = 1)")
: activeFlag === 2
? render("B (flag = 2)")
: activeFlag === 3
? render("C (flag = 3)")
: pass(`flag ${activeFlag} out of range`)
)
: pass(`jobId ${jobId} out of range`)
}
// Defines logging functions for demo purposes
function render(val){ console.log(`Rendering ${val}`); }
function pass(reason){ console.log(`Doing nothing (${reason})`) }
import React from 'react';
import ListView from './ListView';
import TableView from './TableView';
function DataView({
currView,
data,
onSelect,
onChangeStatus,
viewTodo,
editTodo,
deleteTodo,
}) {
return (
<div>
{(function () {
switch (currView) {
case 'table':
return (
<TableView
todos={data}
onSelect={onSelect}
onChangeStatus={onChangeStatus}
viewTodo={viewTodo}
editTodo={editTodo}
deleteTodo={deleteTodo}
/>
);
case 'list':
return (
<ListView
todos={data}
onSelect={onSelect}
onChangeStatus={onChangeStatus}
viewTodo={viewTodo}
editTodo={editTodo}
deleteTodo={deleteTodo}
/>
);
default:
break;
}
})()}
</div>
);
}
export default DataView;
尽管这是另一种方法,但如果您已经完全使用了钩子,则可以利用useCallback来生成一个只在必要时重新创建的函数。
假设你有一个组件,它应该根据状态道具来呈现。使用钩子,你可以这样实现:
const MyComponent = ({ status }) => {
const renderContent = React.useCallback(() => {
switch(status) {
case 'CONNECTING':
return <p className="connecting">Connecting...</p>;
case 'CONNECTED':
return <p className="success">Connected Successfully!</p>
default:
return null;
}
}, [status]);
return (
<div className="container">
{renderContent()}
</div>
);
};
我喜欢这个是因为:
It's obvious what is going on - a function is created, and then later called (the immediately invoked anonymous function method looks a little odd, and can potentially confuse newer developers)
The useCallback hook ensures that the renderContent callback is reused between renders, unless the depedency status changes
The renderContent function uses a closure to access the necessary props passed in to the component. A separate function (like the accepted answer) requires the passing of the props into it, which can be burdensome (especially when using TypeScript, as the parameters should also be typed correctly)
我正在使用这个帮助器,它允许我在JSX中有开关语句
// in helpers folder
const switchTrue = (object) => {
const { default: defaultValue, ...rest } = object;
const obj = { default: defaultValue, ...rest };
const result = Object.keys(obj).reduce((acc, cur) => {
return {
...acc,
[cur === 'default' ? 'true' : cur]: obj[cur],
};
}, {});
return result['true'];
};
const Sample = () => {
const isDataLoading = false;
return (
<div>
{
switchTrue({
[`${isDataLoading}`]: <div>Loading</div>,
[`${!isDataLoading}`]: <div>Data Ready</div>,
default: <div>Default</div>,
})
}
</div>
)
}
ReactDOM.render(
<Sample/>,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="react"></div>
这个助手应该可以做到这一点。
使用示例:
{componentSwitch(3, (switcher => switcher
.case(1, () =>
<p>It is one</p>
)
.case(2, () =>
<p>It is two</p>
)
.default(() =>
<p>It is something different</p>
)
))}
助手:
interface SwitchCases<T> {
case: (value: T, result: () => React.ReactNode) => SwitchCases<T>;
default: (result: () => React.ReactNode) => SwitchCases<T>;
}
export function componentSwitch<T>(value: T, cases: (cases: SwitchCases<T>) => void) {
var possibleCases: { value: T, result: () => React.ReactNode }[] = [];
var defaultResult: (() => React.ReactNode) | null = null;
var getSwitchCases: () => SwitchCases<T> = () => ({
case: (value: T, result: () => React.ReactNode) => {
possibleCases.push({ value: value, result });
return getSwitchCases();
},
default: (result: () => React.ReactNode) => {
defaultResult = result;
return getSwitchCases();
},
})
// getSwitchCases is recursive and will add all possible cases to the possibleCases array and sets defaultResult.
cases(getSwitchCases());
// Check if one of the cases is met
for(const possibleCase of possibleCases) {
if (possibleCase.value === value) {
return possibleCase.result();
}
}
// Check if the default case is defined
if (defaultResult) {
// Typescript wrongly assumes that defaultResult is always null.
var fixedDefaultResult = defaultResult as (() => React.ReactNode);
return fixedDefaultResult();
}
// None of the cases were met and default was not defined.
return undefined;
}