React Hooks: From Mixins and HOCs to Custom Hook Implementations and Production Practices
This article explains the evolution of React component reuse—from legacy mixins and higher‑order components to modern React Hooks—provides step‑by‑step implementations of custom hooks such as useLogTime, useFetchHook, useInterval and useImgLazy, and demonstrates how to apply them in real‑world production code for better performance and maintainability.
The author, a former front‑end engineer at Ctrip’s overseas ride‑hailing team, shares a comprehensive tutorial on React component reuse and the transition to React Hooks.
1. Before React Hooks
React’s component model enables Lego‑like UI composition, but as business logic grows, mixing lifecycle methods with business code leads to duplication and maintenance pain. The article first revisits two older reuse patterns.
1.1 React.mixin
Mixins were used with React.createClass to merge shared methods into a component’s prototype. They are now discouraged because they are hard to trace and hurt performance. Example:
var logMixin = {
alertLog: function(){
alert('alert mixin...');
},
componentDidMount: function(){
console.log('mixin did mount');
}
};
var MixinComponentDemo = React.createClass({
mixins: [logMixin],
componentDidMount: function(){
document.body.addEventListener('click',()=>{ this.alertLog(); });
console.log('component did mount');
}
});
// console output:
// component did mount
// mixin did mount
// (click page) alert mixin1.2 Higher‑Order Components (HOC)
HOCs are functions that take a component and return a new component, allowing logic such as logging and timing to be shared without inheritance. Example implementation:
function logTimeHOC(WrappedComponent, options = {time:true, log:true}){
return class extends React.Component {
constructor(props){
super(props);
this.state = {index:0};
this.show = 0;
}
componentDidMount(){
options.time && (this.timer = setInterval(()=>{ this.setState({index: ++index}); },1000));
options.log && console.log('组件渲染完成----');
}
componentDidUpdate(){
options.log && console.log(`我背更新了${++this.show}`);
}
componentWillUnmount(){
this.timer && clearInterval(this.timer);
options.log && console.log('组件即将卸载----');
}
render(){
return
;
}
};
}Using the HOC, three concrete components are created (LogComponent, SetTimeComponent, LogTimeShowComponent) by wrapping simple presentational components.
2. React Hooks
Hooks solve the same reuse problems while allowing function components and eliminating the need for this . A custom hook useLogTime is introduced to encapsulate logging and timing logic:
import React, { useState, useEffect } from 'react';
function useLogTime(data = {log:true, time:true}){
const [count, setCount] = useState(0);
useEffect(() => {
data.log && console.log('组件渲染完成----');
let timer = null;
if(data.time){
timer = setInterval(() => setCount(c=>c+1), 1000);
}
return () => {
data.log && console.log('组件即将卸载----');
data.time && clearInterval(timer);
};
}, []);
return {count};
}The article then walks through building a minimal useState and useEffect from scratch to illustrate how hooks work internally, showing the need for a global memoizedState array and a currentCursor to keep state isolated per hook call.
let memoizedState = [];
let currentCursor = 0;
function useState(initVal){
memoizedState[currentCursor] = memoizedState[currentCursor] || initVal;
const setVal = newVal => { memoizedState[currentCursor] = newVal; render(); };
return [memoizedState[currentCursor++], setVal];
}
function useEffect(fn, watch){
const hasWatchChange = memoizedState[currentCursor]
? !watch.every((val,i)=> val===memoizedState[currentCursor][i])
: true;
if(hasWatchChange){
fn();
memoizedState[currentCursor] = watch;
currentCursor++;
}
}Diagrams (omitted) illustrate how hook calls are stored sequentially in the array, which explains the rule that hooks must be called at the top level of a component.
3. React in Production
The article maps class lifecycle methods to Hook equivalents (e.g., componentDidMount → useEffect(()=>{},[]) ) and shows common patterns such as data fetching with a custom useFetchHook that returns {data, status} and handles loading, success, and error states.
function useFetchHook(config, watch){
const [data, setData] = useState(null);
const [status, setStatus] = useState(0); // 0 loading, 1 success, 2 error
useEffect(() => {
const fetchData = async () => {
try {
const result = await axios(config);
setData(result.data);
setStatus(1);
} catch(err){
setStatus(2);
}
};
fetchData();
}, watch ? [watch] : []);
return {data, status};
}Performance tips include using useCallback or useMemo to avoid recreating functions on each render, and a custom useInterval hook that abstracts setInterval with proper cleanup:
function useInterval(callback, time=300){
const intervalFn = useRef();
useEffect(() => { intervalFn.current = callback; });
useEffect(() => {
const timer = setInterval(() => { intervalFn.current(); }, time);
return () => clearInterval(timer);
}, [time]);
}Finally, a lazy‑loading image hook useImgLazy is presented, which checks if images are within the viewport using getBoundingClientRect and loads the real source when needed.
function useImgLazy(className){
useEffect(() => {
const onScroll = () => checkImgs(className);
window.addEventListener('scroll', onScroll);
checkImgs(className);
return () => window.removeEventListener('scroll', onScroll);
}, []);
}The article concludes with a list of reference links to official React documentation, deep‑dive articles on Hook internals, and several community posts about best practices.
Ctrip Technology
Official Ctrip Technology account, sharing and discussing growth.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.