React中如何逻辑复用?
函数组件
- 自定义hooks
- React中的自定义hooks是功能扩展,允许在函数组件中,复用状态逻辑,并且无需改变组件结构。
- 本质是js函数,遵循特性规则:必须以use开头;封装业务逻辑,使代码易维护;可使用基础的hook(useState、useRef等);核心要点,把组件的一些逻辑(非状态)提取到可复用的函数中,相同的状态管理+副作用逻辑可在多个组件间共享。
function useForm(initialValues){
const [values, setValues] = React.useState(initialValues);
const handleChange = (event) => {
const {name, value} = event.target;
setValues({
...values,
[name]: value,
});
}
const handleSubmit = (event) => {
event.preventDefault();//阻止默认事件
console.log(values);
}
return {
values,
handleChange,
handleSubmit,
}
}
const LoginForm = () => {
const {values, handleChange, handleSubmit} = useForm({username:'',possword: ''});
return (
<form onSubmit={handleSubmit}>
<>
<label>用户名</label>
<input type='text' name="username" value={values.username} onChange={handleChange} />
</>
<>
<label>密码</label>
<input type='possword' name="possword" value={values.possword} onChange={handleChange} />
</>
<button type='submit'></button>
</form>
)
}
高阶组件
- 高阶组件(HOC,High Order Component)是一个函数式编程函数,参数是一个组件,返回值是一个新组件,新组件具有增强的能力,拥有额外的属性和行为。
- 允许抽象公共的逻辑,避免代码重复。如 给多个组件添加loading效果。
- 不直接修改原组件,可保证原始组件的纯净与复用性。
const withLoading = (WrappedComponent)=>{
return ()=>{
const [isLoading, setIsLoading] = useState(true);
useEffect(()=>{
setTimeout(()=>{
setIsLoading(false);
}, 2000)
}, []);
if(isLoading) {
return <div>loading.....</div>
}
return <WrappedComponent {...props}/>
}
}
function MyComponent() {
return <div>内容加载完成</div>
}
const MyComponentWithLoading = withLoading(MyComponent);
function App() {
return <MyComponentWithLoading />
}
renderProps
- 使用一个值为函数的props属性,该函数返回要渲染的React元素,通过该方式,可在多个组件中共享业务逻辑。
class MouseTracker extends React.Component{
state = {x:0, y:0};
handleMouseMove = (event) =>{
this.setState({
x: event.clientX,
y: event.clientY,
});
render(){
return (
<div style={{height: '100vh'}} onMouseMove={this.handleMouseMove}>
{this.props.render(this.state)}
</div>
)
}
}
}
const App = () => {
return (
<div>
<h1>请移动你的鼠标</h1>
<MouseTracker render={({x, y}) => {
return <div>当前位置:{x},{y}</div>
}} />
</div>
)
}
将上述代码,改写成children实现
{this.props.children(this.state)}
const App = () => {
return (
<div>
<h1>请移动你的鼠标</h1>
<MouseTracker>
{
({x, y}) => {
return <div>当前位置:{x},{y}</div>
}
}
</MouseTracker>
</div>
)
}
总结:
- children 与 renderProps原理是一样的,给组件提供一个渲染虚拟DOM的函数用来决定渲染出来的真实DOM。
- 传递方式、传递位置不一样。props.render,props.children。
类组件
- 与函数式组件一样,HOC可用于类组件封装并复用逻辑。
- 高阶组件可组合、级联、串行、链式调用、组合调用,可用多个高阶组件装饰一个组件;慎用高阶组件级联、嵌套(会出现嵌套地狱or回调地狱)。
- 高阶组件缺点:
- 不能透传静态属性。
- 不能透传ref属性,因为不会像其他属性一样自动透传给被包裹的属性组件(解决:可使用React.forwardRef进行转发,允许将ref自动转发到另一个组件)。
//提升静态属性
import hoistNonReactStatics from 'hoist-non-react-statics';
function withAuth(WrappedComponent){
class withAuthComponent extends React.Component{
isLogin(){
return !!localStorage.getItem('isLogin');
}
render(){
if(!this.isLogin){
return <div>请登录后再访问此页面</div>
}
return <WrappedComponent />
}
}
//把WrappedComponent类组件上的非React内部静态属性+静态方法传递给withAuthComponent
return withAuthComponent;
}
class MyComponent extends React.Component{
static someStaticProperty = 'some value';
static someStaticMethod = ()=>{
console.log('someStaticMethod');
}
render(){
return <div>欢迎来到受保护页面</div>
}
}
const ProtectedComponent = withAuth(MyComponent);
console.log('ProtectedComponent.someStaticProperty',ProtectedComponent.someStaticProperty);
ProtectedComponent.someStaticMethod();
渲染劫持(高阶组件概念)
- 在不改变被包裹的组件本身情况下,改变其渲染输出的结果。
- 条件渲染:根据特定的条件改变渲染的结果or决定是否渲染。
- 属性操作:可向被包裹的组件传递新的属性or修改旧属性。
- 结构变更:改变组件的DOM结构与样式。
- 通过继承实现渲染劫持
//继承实现渲染劫持
class MyComponent extends React.Component{
render(){
return (
<h1 style={this.props.style}>hello,{this.props.name}</h1>
)
}
}
function withRenderHijacking(WrappedComponent){
return class extends WrappedComponent{
render(){
if(this.props.loading){
return <div>loading.....</div>
}
//调用旧的render方法返回旧的react元素
const oldElement = super.render();
return (
<div style={{color:'red'}}>
<h2>Hijacked</h2>
{
React.cloneElement(oldElement,{
style:{color:'green'}
},`hello,hijack`)
}
</div>
)
}
}
}
const HijackedComponent = withRenderHijacking(MyComponent);
类组件继承(不推荐)
设计模式原则:组合优于继承。继承耦合度太高。
Mixin(已废弃)
- React早期版本中,Mixin用来共享组件逻辑。