下面我们再来封装一个步骤条的 hook
原代码
import { Steps, Button, message } from 'antd';
const { Step } = Steps;
const steps = [
{
title: 'First',
content: 'First-content',
},
{
title: 'Second',
content: 'Second-content',
},
{
title: 'Last',
content: 'Last-content',
},
];
const App = () => {
const [current, setCurrent] = React.useState(0);
const next = () => {
setCurrent(current + 1);
};
const prev = () => {
setCurrent(current - 1);
};
return (
<>
<Steps current={current}>
{steps.map((item) => (
<Step key={item.title} title={item.title} />
))}
</Steps>
<div className="steps-content">{steps[current].content}</div>
<div className="steps-action">
{current < steps.length - 1 && (
<Button type="primary" onClick={() => next()}>
Next
</Button>
)}
{current === steps.length - 1 && (
<Button type="primary" onClick={() => message.success('Processing complete!')}>
Done
</Button>
)}
{current > 0 && (
<Button style={{ margin: '0 8px' }} onClick={() => prev()}>
Previous
</Button>
)}
</div>
</>
);
};
下面我们来一步步实现
import React, { useCallback, useState, useEffect } from 'react';
import { Steps } from 'antd';
const { Step } = Steps;
// 这里我们先实现一个包裹step容器,通过上级传来的children与currentStep来判断显示对应的steps-content即显示内容区域
const StepContainer = (props) => {
const [stepContent, setStepContent] = useState([]);
const [stepNameList, setStepNameList] = useState([]);
const { children, currentStep } = props;
// 当children发生变化的时候更新步骤条
const initStepNameList = useCallback(() => {
const PropsStepNameList = React.Children.map(children, (child, index) => {
return {
title: child.props.title,
key: child.props.key || `childStep${index}`,
};
});
setStepNameList(PropsStepNameList);
}, [children]);
// 在上级currentStep当前步数与children判断显示对应的steps-content
const initStepContent = useCallback(() => {
const propsStepContent = React.Children.map(children, (child, index) => {
if (index === currentStep) {
return {
...child,
};
}
return null;
});
setStepContent(propsStepContent);
}, [currentStep, children]);
// 钩子 发生变化 且初始化时执行
useEffect(() => {
initStepNameList();
initStepContent();
}, [initStepNameList, initStepContent]);
// 显示对应内容
return (
<>
<Steps current={currentStep}>
{stepNameList.map((step) => (
<Step key={step.key} title={step.title} />
))}
</Steps>
{stepContent}
</>
);
};
// 引用
const App = () => {
const [currentStep, setCurrentStep] = useState(0);
const changeStep = (stepNum) => {
setCurrentStep(stepNum);
};
return (
<StepContainer currentStep={currentStep}>
<span title="First">First-content</span>
<span title="Second">Second-content</span>
<span title="Last">Last-content</span>
</StepContainer>
);
};
上述例子中,我们把处理 stepNameList、stepContent 的操作,都放在了 StepContainer hook 中;
我们还可以把复杂操作,通过 hooks 提取出去;将组件中关联部分拆分;
import React, { useCallback, useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { Card, Steps } from 'antd';
import useStep from '@/hooks/useStep';
const { Step } = Steps;
const useStep = (props) => {
const [stepContent, setStepContent] = useState([]);
const [stepNameList, setStepNameList] = useState([]);
const { children, currentStep } = props;
// 当children发生变化的时候更新步骤条
const initStepNameList = useCallback(() => {
const PropsStepNameList = React.Children.map(children, (child, index) => {
return {
title: child.props.title,
key: child.props.key || `childStep${index}`,
};
});
setStepNameList(PropsStepNameList);
}, [children]);
// 在上级currentStep当前步数与children判断显示对应的steps-content
const initStepContent = useCallback(() => {
const propsStepContent = React.Children.map(children, (child, index) => {
if (index === currentStep) {
return {
...child,
};
}
return null;
});
setStepContent(propsStepContent);
}, [currentStep, children]);
// 钩子
useEffect(() => {
initStepNameList();
initStepContent();
}, [initStepNameList, initStepContent]);
return [currentStep, stepContent, stepNameList];
};
// 步骤条容器 在这里直接使用useStep 去判断显示对应内容
const StepContainer = (props) => {
const [currentStep, stepContent, stepNameList] = useStep(props);
return (
<>
<Steps current={currentStep}>
{stepNameList.map((step) => (
<Step key={step.key} title={step.title} />
))}
</Steps>
{stepContent}
</>
);
};
// 引用
const App = () => {
const [currentStep, setCurrentStep] = useState(0);
const changeStep = (stepNum) => {
setCurrentStep(stepNum);
};
return (
<StepContainer currentStep={currentStep}>
<span title="First">First-content</span>
<span title="Second">Second-content</span>
<span title="Last">Last-content</span>
</StepContainer>
);
};
我们在上述 StepContainer 组件中再次将它的副作用操作拆分;在此组件中只关注渲染结果,useStep 劫持 props,返回一个数组,数组中包括 currentStep, stepContent, stepNameList;在我们使用 useStep 时,会根据我们 props 的不同而返回不同的状态
下面我们先来实现一个 loading 的 hook
TestCount 的需求为点击增加,显示 loading,两秒后显示为增加后的数值;点击减少,显示 loading,两秒后显示为减少后的数值
import React, { useState, useEffect } from 'react';
const AsyncCount = ({ countNum }) => {
const [loading, setLoading] = useState(true);
const [count, setCount] = useState(0);
// 当传下来的countNum 发生变化的时候 我们回去更改自身组件的count 与loading状态
useEffect(() => {
setLoading(true);
setTimeout(() => {
setLoading(false);
setCount(countNum);
}, 2000);
}, [countNum]);
return <>{loading ? <p>Loading...</p> : <p>{count}</p>}</>;
};
// 在此组件中使用AsyncCount组件
const TestCount = () => {
const [count, setCount] = useState(0);
const changeCount = (name) => {
setCount(name);
};
return (
<>
<AsyncCount countNum={count} />
<button
onClick={() => {
changeCount(count + 1);
}}
>
增加
</button>
<button
onClick={() => {
changeCount(count - 1);
}}
>
减少
</button>
</>
);
};
export default TestCount;
再上述例子中,我们把处理 count 异步的操作以及是否渲染 loading,都放在了 AsyncCount hook 中;
我们还可以把复杂操作,通过 hooks 提取出去;将组件中关联部分拆分;
那下面我们在做一个更加细化的拆分,拆出一个自己的 hook
import React, { useState, useEffect } from 'react';
// 我们把此处变化count与loading的hook拆出来
const useCount = (countNum) => {
const [loading, setLoading] = useState(true);
const [count, setCount] = useState(0);
// 在传入进来countNum发生变化的时候,重新return 出去新的loading与count
useEffect(() => {
setLoading(true);
setTimeout(() => {
setLoading(false);
setCount(countNum);
}, 2000);
}, [countNum]);
return [loading, count];
};
// 此组件中只拿到countNum 使用useCount hook 去除对应的loading与count
const AsyncCount = ({ countNum }) => {
const [loading, count] = useCount(countNum);
return <>{loading ? <p>Loading...</p> : <p>{count}</p>}</>;
};
// 此组件中使用AsyncCount组件
const TestCount = () => {
const [count, setCount] = useState(0);
const changeCount = (count) => {
setCount(count);
};
return (
<>
<AsyncCount countNum={count} />
<button
onClick={() => {
changeCount(count + 1);
}}
>
增加
</button>
<button
onClick={() => {
changeCount(count - 1);
}}
>
减少
</button>
</>
);
};
export default TestCount;
我们在上述 AsyncCount 组件中再次将它的副作用操作拆分;在此组件中只关注渲染结果,useCount 接受一个数字,返回一个数组,数组中包括状态,与 count 两个结果;在我们使用 useCount 时,会根据我们传入参数的不同而返回不同的状态;