react中hooks分享

一. HOOKS是什么

在计算机程序设计中,钩子一词涵盖了一系列技术,这些技术用来通过拦截函数调用、消息或在软件组件之间传递的事件来改变或增加操作系统、应用程序或其他软件组件的行为。处理这些被截获的函数调用、事件或消息的代码称为“hook”。
在这里插入图片描述
在react中,有两种组件:类(class)组件 和 函数(function)组件。

类(class)是数据和逻辑的封装。 也就是说,组件的状态和操作方法是封装在一起的。如果选择了类的写法,就应该把相关的数据和操作,都写在同一个 class 里面。

函数一般来说,只应该做一件事,就是返回一个值。 如果你有多个操作,每个操作应该写成一个单独的函数。而且,数据的状态应该与操作方法分离。根据这种理念,React 的函数组件只应该做一件事情:返回组件的 HTML 代码,而没有其他的功能。这种只进行单纯的数据计算(换算)的函数,在函数式编程里面称为 “纯函数”(pure function)。

**函数式编程将那些跟数据计算无关的操作,都称为 “副效应” 。**如果函数内部直接包含产生副效应的操作,就不再是纯函数了,我们称之为不纯的函数。纯函数内部只有通过间接的手段(即通过其他函数调用),才能包含副效应。

钩子(hook)就是 React 函数组件的副效应解决方案,用来为函数组件引入副效应。 函数组件的主体只应该用来返回组件的 HTML 代码,所有的其他操作(副效应)都必须通过钩子引入。

Hooks使得react可在不编写类组件的情况下使用 state(状态) 和其他 React 功能。

二. 为什么要有hooks

  • 在组件之间重用有状态逻辑很困难
    React 没有为复用状态逻辑提供原生途径。通常类组件的逻辑复用会使用 HOC (高阶组件)或 render props 的方案,但是此类方案通常需要你重新组织组件结构,且过多的嵌套抽象层组件很容易形成“嵌套地狱”。
    使用 Hook ,可以从组件中提取有状态逻辑,以便可以独立测试并重用。Hooks 允许在不更改组件层次结构的情况下重用有状态逻辑。
// 例如在对于接口请求的情况,每个页面都需要在componentDidMount中调用接口,调用接口时需要将state中的loading置为true,结束后,再置为false。
class Test extends PureComponent {state = {loading: false,data: null}componentDidMount() {this.setState({loading: true,})fakeGet("xxx.com/xxx").then(res => {this.setState({data: res,loading: false})})}render() {const { loading, data } = this.state;return (<div>{loading ? <Loading /> : (data.map(item => (<Item data={item} />)))}</div>)}
}// 因为类组件的state是自身特有的,所以不能直接复用,因此每个类组件都需要写一遍这个逻辑// 如果使用hooks呢
const Test = ({}) => {const [loading, setLoading] = useState(false);const [data, setData] = useState(null);    useEffect(() => {setLoading(true);fakeGet("xxx.com/xxx").then(res => {setData(res);setLoading(false)})})return (<div>{loading ? <Loading /> : (data.map(item => (<Item data={item} />)))}</div>)
}// 这时可以把状态提取至公共状态
const useRequest = (option) => {const { url, ...opt } = option;const [loading, setLoading] = useState(false);const [data, setData] = useState(null);useEffect(() => {setLoading(true);fakeGet(url, opt).then(res => {setData(res);setLoading(false)})})return { loading, data };
}
const Test = ({}) => {const { loading, data } = useRequest({url: "xxxx.com/xxx",method: "GET",})return (<div>{loading ? <Loading /> : (data.map(item => (<Item data={item} />)))}</div>)
}
// 之后需要做接口请求的地方都可以使用useRequest这个hooks,不用重复定义loading等状态。
  • 复杂的组件变得难以理解
    我们常常不得不维护一些组件,这些组件一开始很简单,但后来却变成了一堆难以管理的有状态逻辑和副作用。每个生命周期方法通常包含一组不相关的逻辑。例如,
    组件可能在componentDidMount 和 componentDidUpdate中执行一些数据获取。
    相同的 componentDidMount 方法可能还包含一些不相关的逻辑,它们设置事件监听器,并在 componentWillUnmount 中执行清理。
    一起更改的相互关联的代码会被分离,但是完全不相关的代码最终会组合在一个方法中。这很容易引入错误和不一致。
    Hooks可以根据相关内容(例如设置订阅或获取数据)将一个组件拆分为较小的函数,而不是基于生命周期方法强制拆分。还可以选择使用 useReducer 管理组件的本地state(状态),以使其更具可预测性。
    虽然hooks可以模拟出大部分生命周期,但是像 getSnapshotBeforeUpdate,getDerivedStateFromError 和 componentDidCatch 等生命周期 API,使用 Hooks 不能完全替代。

三. hooks、HOC、render Props对于封装的差异

1. HOC - 高阶组件

如下是一个常见 HOC 的用法。使用 connect 连接 store, 使用 withRouter 获取路由参数,这种嵌套的写法可读性和可维护性非常差(才两层嵌套就很难受了),虽然可以使用 compose 组合高阶组件,或者装饰器简化写法,但本质还是 HOC 的嵌套。

const App = withRouter(connect(commentSelector)(WrappedComponent))// 优化 可以使用一个 compose 函数组合HOC 
const enhance = compose(withRouter, connect(commentSelector))const App = enhance(WrappedComponent)// 优化 使用装饰器  
@connect  
class App extends React.Component {}

每一次 HOC 调用都会产生一个组件实例,多层嵌套会增加React虚拟Dom的深度并且影响性能,此外包裹太多层级之后,可能会带来props属性的覆盖问题。此外,HOC 对于使用者更像是一个黑盒,通查需要看具体的实现来使用。

2. Render Props

如下是复用监听 window size 变化的逻辑

<WindowSize> (size) => <OurComponent size={size} /> </WindowSize>然后,如果再想复用监听鼠标位置的逻辑
<WindowSize> 
(size) => ( <Mouse> (position) => <OurComponent size={size} mouse={position} /> </Mouse> ) 
</WindowSize>

到这里可能不会再想复用其他逻辑了,虽然 render props 解决了 hoc 存在的一些问题,比如对使用者黑盒,属性名覆盖等,但是使用 render props 时,如果复用逻辑过多会仍然会导致嵌套过深,形成回调地狱。

3. Hooks - 为复用状态逻辑提供原生途径

// 复用监听 window size 变化的逻辑 const size = useSize() // 复用监听鼠标位置的逻辑 const position = useMouse()

用自定义 Hooks 改写之后,难道不“香”吗,谁还想回头写 HOC 和 render props。自定义 Hooks 复用状态逻辑的方式得到 React 原生的支持,与React组件不同的是,自定义 Hooks 就是一个以 use 开头的函数,因此也更易于测试和复用。除此之外,在“真香”的自定义 Hooks 中也可以使用其他 Hooks。

四. 基础hooks

useState(状态钩子)

initialValue可以传一个函数,然后将初始值return出来。
setState不会帮你自动merge数据,如

const [data, setState] = useState({a:1, b:2})setState({ c: 1 });
// state会被改成{c:1},而不是{a:1, b:2, c:1}

setState会使用Object.is来判断前后状态是否相同,相同时不会触发渲染
多次setState或者不同useState的setState方法,如果在React“可控”流程中(比如同步的事件回调、useEffect同步函数中等),会进行优化,只会触发一次渲染

const Demo4 = () => {const [number, setNumber] = useState(0);// 第一次为0// effect后为 1// click后为 4,说明多个setState进行了合并,而回调函数的setState将正常改变// 再增加一个setTimeout会怎么样?console.log(0, number);useEffect(() => {setNumber(number + 1);console.log('1', number);    // 0}, [])function handleAdd() {setNumber(number + 1);console.log(2, number);    // 1setNumber(number + 1);console.log(3, number);    // 1setNumber(number + 1);console.log(4, number);    // 1setNumber((prev) => {console.log('prev', prev);    // 2return prev + 1;})setNumber((prev) => {console.log('prev1', prev);    // 3return prev + 1;})// 如果增加下面这个,会发生什么呢?// setTimeout(() => {//   setNumber(number + 1);//   console.log(6, number);      // ??// }, 0)console.log(5, number);    // 1}return (<div><p>number: {number}</p><button onClick={handleAdd}>+++</button></div>)
}

粒度问题
根据逻辑模块划分,如果多个state相关联,建议封装在一起 例如:pagination state中的current、total、pageSize等状态
考虑性能优化进行划分,尽量避免无意义渲染 例如:request state中的loading、dataSource、error等状态
同时也要兼顾代码可维护性,不要和类组件一样,把所有state都塞在一起 例如:table state中的pagiantion、query、selection等状态
如果状态实在过多而且又想封装在一个State中,考虑使用useReducer 采用redux中的store、dispatch方式去更好地管理状态
与类组件中state的区别

// 类组件下addHandleTimeout2 = () => {const { count } = this.state;console.log(`----timeout count ---- ${count}`) // 0this.setState({count:count + 1});setTimeout(() => {console.log(`----this.state.count---- ${this.state.count}`); // 1console.log(`----count---- ${count}`); // 0}, 2000);
}// hook function component
const addHandleTimeout2 = () => {console.log(`----timeout count ---- ${count}`) // 0setCount(count + 1);setTimeout(() => {console.log(`----count---- ${count}`); // 0}, 2000);
}
// 会输出什么?count初始值为0。

首先是对 class component 的解释:
state 是 Immutable 的,setState 后一定会生成一个全新的 state 引用。
但 Class Component 通过 this.state 方式读取 state,这导致了每次代码执行都会拿到最新的 state 引用,所以快速点击4次的结果是 4 4 4 4。
然后是对 function component useState 的解释:
useState 产生的数据也是 Immutable 的,通过数组第二个参数 Set 一个新值后,原来的值在下次渲染时会形成一个新的引用。
但由于对 state 的读取没有通过 this. 的方式,使得 每次 setTimeout 都读取了当时渲染闭包环境的数据,虽然最新的值跟着最新的渲染变了,但旧的渲染里,状态依然是旧值。

2. useReducer(action 钩子)

React 本身不提供状态管理功能,通常需要使用外部库。这方面最常用的库是 Redux。
Redux 的核心概念是,组件发出 action 与状态管理器通信。状态管理器收到 action 以后,使用 Reducer 函数算出新的状态,Reducer 函数的形式是(state, action) => newState。
useState的替代方案。同样接受类型为 (state, action) => newState 的reducer,并返回与 dispatch 方法配对的当前状态。
官方推荐把 state 切分成多个 state 变量,每个变量包含的不同值会在同时发生变化
在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等,并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化(因为useState对于值的更新是直接替换,而不做合并处理,如果遇到深层级更新的操作,会比较麻烦,没有useReducer给力)。
除此之外还有一个好处,Reducer其实一个与UI无关的纯函数,useReducer的方案使得我们更容易构建自动化测试用例。

// 使用方式如下const initialState = {count: 0};
function reducer(state, action) {switch (action.type) {case 'increment':return {count: state.count + 1};case 'decrement':return {count: state.count - 1};default:throw new Error();}
}function Counter() {const [state, dispatch] = useReducer(reducer, initialState);return (<>Count: {state.count}<button onClick={() => dispatch({type: 'decrement'})}>-</button><button onClick={() => dispatch({type: 'increment'})}>+</button></>);
}

3. useEffect(副作用钩子)

使用useEffect可以模拟很多class组件中的生命周期,如componentDidMount,componentDidUpdate, componentWillUnmount等
与 componentDidMount、componentDidUpdate 不同的是,传给 useEffect 的函数会在浏览器完成布局与绘制之后,在一个延迟事件中被调用。这使得它适用于许多常见的副作用场景,比如设置订阅和事件处理等情况,因为绝大多数操作不应阻塞浏览器对屏幕的更新。
使用方式
第一个参数为副作用函数 副作用函数可以选择返回一个函数,会在下一次执行该副作用或组件注销时调用
第二个参数为依赖数组,选填参数,在依赖变化时会触发副作用函数重新执行
如果依赖数组不传,则组件每次render时都会执行 ,而传递一个空数组时,则只会在组件创建时被执/行一次。
副作用函数在任何情况下一定会调用至少一次

const Demo5 = () => {const [name, setName] = useState('');useEffect(() => {console.log('name:', name)}, [name])return (<div><p>name: {name}</p><button onClick={() => setName("aaa")}>change</button></div>)
}

闭包问题:每一次渲染执行的effect拿到的都是当次渲染的最新变量,而clean up拿到的是上次渲染时的旧变量
使用 useEffect 完成副作用操作。赋值给 useEffect 的函数会在组件渲染到屏幕之后执行。你可以把 effect 看作从 React 的纯函数式世界通往命令式世界的逃生通道。
实际操作中,可以使用useEffect来对状态进行监听,当监听的状态发生改变后,便会执行方法。
与useEffect类似的还有一个useLayoutEffect,它会在所有的 DOM 变更之后同步调用 effect,可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect内部的更新计划将被同步刷新。这也将阻塞了浏览器的绘制。
当你的useEffect里面的操作需要处理DOM,并且会改变页面的样式,就需要用这个,否则可能会出现出现闪屏问题(比如根据状态去计算宽高,或者位置的时候,需要使用useLayoutEffect,其余90%以上的场景都只需要使用useEffect)

4. useCallback/useMemo

保证变量稳定,性能优化避免无意义渲染
deps数组必填,如果不填则无使用意义
绝大多数情况下,只要使用到的state和props及衍生变量,必须包含在deps数组里,否则拿到的永远是初始状态的值
如果出满足以下情况,不需要memo:
值未被其他hooks依赖
值未传入其他组件作为props
值为简单类型且计算基本无消耗

const memoizedCallback = useCallback(() => {doSomething(a, b);},[a, b]);// 只要a b不发生改变,这个值也不会发生改变,computeExpensiveValue就只会执行一次
// 主要是用来缓存计算量比较大的函数结果,可以避免不必要的重复计算
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
return (<div onClick={memoizedCallback}>{memoizedValue}</div>
)

5. useRef

保存变量,区别于state,值改变不会触发渲染
值修改时不会触发渲染,所以用来保存不希望触发渲染的变量

function TextInputWithFocusButton() {const inputEl = useRef(null);const onButtonClick = () => {// `current` 指向已挂载到 DOM 上的文本输入元素inputEl.current.focus();};return (<><input ref={inputEl} type="text" /><button onClick={onButtonClick}>Focus the input</button></>);// 也可以直接给ref.current赋值,如下方的例子usePreviousValue,通过ref来保存之前的值
}// ref的穿透操作,父级使用子组件中的方法
const Parent = () => {const childRef = useRef(null);const handleClick = () => {if (childRef) {childRef.current.fff();}};return (<div><Child ref={childRef} /><button onClick={handleClick}>click</button></div>);
};// 子组件中需要使用forwardRef包裹一下,在props中是获取不到ref的值,refs 不会透传下去。
// 这是因为 ref 不是 prop 属性。就像 key 一样,其被 React 进行了特殊处理
// 否则你就需要改变一下ref的名字,如aref等,避开关键字,就可以在props中拿到了const Child = forwardRef((props, ref) => {const currentRef = useRef(null);const [number, setNumber] = useState(0);useImperativeHandle(ref, () => ({fff() {currentRef.current.focus();setNumber(number + 1);}}));return (<><p>number: {number}</p><input ref={currentRef} /></>);
});

6. useContext(共享状态钩子)

传入一个context,可以直接获取到其value

const themes = {light: {foreground: "#000000",background: "#eeeeee"},dark: {foreground: "#ffffff",background: "#222222"}
};const ThemeContext = React.createContext(themes.light);
function App() {return (<ThemeContext.Provider value={themes.dark}><Toolbar /></ThemeContext.Provider>);
}function Toolbar(props) {return (<div><ThemedButton /></div>);
}function ThemedButton() {const theme = useContext(ThemeContext);return (<buttonstyle={{background: theme.background,color: theme.foreground}}>I am styled by theme context!</button>);
}

7. memo(使组件可以记忆化)

类似于class组件中的shouldComponentUpdate,用于根据prevProps与nextProps进行对比,来判断是否需要更新内部组件。
与shouldComponentUpdate不同的是,shouldComponentUpdate返回true时才会更新,而memo返回true表示不更新。
尽可能在所有的组件外部加上memo方法

const Test = () => <div>test</div>
export default memo(Test, (prevProps, nextProps) => {if (prevProps.xxx === nextProps.xxx) {return true;}return false;
})

五. Hooks的规范

1. 只在最顶层使用 Hook

不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层以及任何 return 之前调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。后面简易版的实现原理中会讲到。

2. 只在 React 函数中调用 Hook

不要在普通的 JavaScript 函数中调用 Hook。你可以:

  • 在 React 的函数组件中调用 Hook
  • 在自定义 Hook 中调用其他 Hook

3. 自定义 Hook 必须以 “use” 开头

自定义 Hook 是一种重用状态逻辑的机制(例如设置为订阅并存储当前值),所以每次使用自定义 Hook 时,其中的所有 state 和副作用都是完全隔离的。
实现一个hooks,理解其原理

// 实例代码
function App() {// index = 0;const [count, setCount] = useState(0);// const [count2, setCount2] = useState(0);return (<div><div>{count}</div><ButtononClick={() => {setCount(count + 1);}}>点击</Button>// <div>{count2}</div>// <Button//   onClick={() => {//     setCount2(count2 + 1);//    }}// >//   点击// </Button></div>);
}// 先来一个useState,但是setState后数据并没有更新,原因是每次都被初始化了,所以要将值记录在外部,优先读取外部数据,没有的话,在使用初始化数据,这样就保证了数据的持久性。
let value;
function useState(initialValue) {var state = initialValue;value = value || initialValue;function setState(newState) {value = newState;render();}return [value, setState];
}// 第二步,这个useState只能写一个,写第二个useState的时候,就会覆盖掉前面的,所以再改一下
let memoizedState = [];
let cursor = 0;
function useState(initialValue) {const currentIndex = cursor;cursor++;memoizedState[currentIndex] = memoizedState[currentIndex] || initialValue;function setState(newState) {memoizedState[currentIndex] = newState;render(); // 模拟 reRender,这一行不需要关心}return [memoizedState[currentIndex], setState];
}

下面来实现一个useEffect,我们知道 useEffect 有几个特点:

有两个参数 callback 和 dependencies 数组
如果 dependencies 不存在,那么 callback 每次 render 都会执行
如果 dependencies 存在,只有当它发生了变化, callback 才会执行,初始化时都会执行一下。

function useEffect(callback, depArray) {const currentIndex = cursor;cursor ++;// 从数组中取出上次保存的值,用于此次判断const { callback: unmountCallback , depArray: oldDepArray } = memoizedState[currentIndex] || {};unmountCallback && unmountCallback();// 没有依赖项,或者依赖项中有一个发生改变,都需要触发callbackconst noDep = !depArray;const dspHaveChange = oldDepArray ? !!depArray && depArray.some((item, index) => item !== oldDepArray[index]) : true;const newEffect = {};newEffect.depArray = depArray;if(noDep || dspHaveChange) {newEffect.callback = callback();}memoizedState[currentIndex] = newEffect;
}

此时我们应该可以解答一个问题:

Q:为什么第二个参数是空数组,相当于 componentDidMount ?
A:因为依赖一直不变化,callback 不会二次执行。

React 中是通过类似单链表的形式来代替数组的。通过 next 按顺序串联所有的 hook。

六. 自定义hooks

1. useDidMount

// 利用useEffect的特性
function useDidMount(fn) {useEffect(() => {fn()}, [])
}

2. useWillUnmount

// 利用useEffect的第一个参数的返回值会在每次渲染前执行的特性,模拟卸载组件。
// useRef则可以持久保存数据,且不触发render
function useWillUnmount(fn) {const fnRef = useRef(null);fnRef.current = fn;useEffect(() => {return () => {fnRef.current();} }, [])
}

3. useForceUpdate

// 用于刷新本组件
function useForceUpdate() {const [, setState] = useState(false);const forceUpdate = useCallback(() => {setState((v) => !v);})return forceUpdate;
}// antd 版
export default function useForceUpdate() {const [, forceUpdate] = useReducer(x => x + 1, 0);return forceUpdate;
}

4. usePreviousValue

// 获取上一次render时某个变量的值
function usePreviousValue(value) {const currentRef = useRef(null);const prevRef = useRef(null);// 1、直接改变prevRef.current = currentRef.current;currentRef.current = value;// 2、获取与之前不一样的值const shouldUpdate = !Object.is(current.value, value);if (shouldUpdate) {prevRef.current = currentRef.current;currentRef.current = value;}return prevRef.current;
}

5. useBoolean

// 可以用于切换modal的visible属性
function useBoolean(initValue) {const [state, setState] = useState(initValue || false);const actions = useMemo(() => {return {setTrue() {setState(true);},setFalse() {setState(false);},toggle() {setState((v) => !v);},setValue(value) {setState(value);},}}, [])return [state, actions]
}// 这个方法也可以使用useReducer改造
const reducer = (state, action) => {switch(action.type) {case 'true':return true;case 'false':return false;case 'toggle':return !state;case 'set':return action.type;} 
}
const [state, dispatch] = useReducer(reducer, false);

6. useRequest

function useRequest(id) {const [loading, setLoading] = useState(false);const [body, setBody] = useState(null);const count = useRef(0);useEffect(() => {const currentCount = count.current;setLoading(true);getData(id).then(res => {if (currentCount !== count.current) return;setLoading(false);setBody(res);})return () => {count.current ++;}}, [id])return [loading, body];
}const [loading, body] = useRequest(id);

7. useUpdateEffect

// 监听依赖完成渲染后的操作,return的操作是在下次执行该副作用之前调用
const useUpdateEffect = (effect, deps) => {const isMounted = useRef(false);useEffect(() => {// 第一次执行是在mount阶段,故不返回effect,第二次执行之后才会设置effect,在第三次执行前,会执行effect方法if (!isMounted.current) {isMounted.current = true;} else {return effect();}}, deps);
};

七. 推荐使用的hooks的库

  • ahooks,有很多hooks可用,而且有一些和antd相关联的hooks,如useAntdTable,基于 useRequest 实现,加载态,分页都可支持
  • hox:hooks中的状态管理器,代码简单,有兴趣的可以去看一看。
// 定义modal
import { createModel } from 'hox';
/* 任意一个 custom Hook */
function useCounter() {const [count, setCount] = useState(0);const decrement = () => setCount(count - 1);const increment = () => setCount(count + 1);return {count,decrement,increment};
}
export default createModel(useCounter)
// 使用modal
import useCounterModel from "../models/useCounterModel";
function App(props) {const counter = useCounterModel();return (<div><p>{counter.count}</p><button onClick={counter.increment}>Increment</button></div>);
}

八. 如何重构代码

1. 重构的目标

重构的主要目的在于改善既有代码的设计,而不是修改缺陷、新增功能等。

重构可以是修改变量名、重新安排目录这样简单的物理重构,也可以是抽取子函数、精简冗余设计这样稍许复杂的逻辑重构。但均不改变现有代码的功能。

重构可以将意大利面条式的杂乱代码整理为千层饼式的整洁代码。整洁的代码更加健壮,因此便于建立完善的测试防护网。同时,新手老人均可放心地修改。

期望重构之后,代码逻辑一目了然,扩展和修改非常方便,出现故障时能迅速定位和修复。前人摔跤过的地方后人不再栽倒,前人思考出的成果后人可直接借用。总之,高度人性化,极大解放人力和脑力。

2. 什么样的代码一看就懂?

但凡遇到那种看着逻辑代码一大堆放在一起的,就头大,后来发现,这些代码都犯了一个相同的错误。没有分清楚什么是步骤,什么是实现细节。当你把步骤和细节写在一起的时候,灾难也就发生了,尤其是那种长年累月迭代出来的代码,if 遍地。Hooks 是一个做代码拆分的高效工具,但是他也非常的灵活,业界一直没有比较通用行的编码规范,但是我有点不同的观点,我觉得他不需要像 Redux 一样的模式化的编码规范,因为他就是函数式编程,他遵循函数式编程的一般原则,函数式编程最重要的是拆分好步骤和实现细节,这样的代码就好读,好读的代码才是负责任的代码。

到底怎么区分步骤和细节?有一个很简单的方法,在你梳理需求的时候,你用一个流程图把你的需求表示出来,这时候的每个节点基本就是步骤,因为他不牵扯到具体的实现。解释太多,有点啰嗦了,相信你肯定懂,对吧。步骤和细节分清楚以后,对重构也有很大的好处,因为每个步骤都是一个函数,不会有像 class 中 this 这种全局变量,当你需要删除一个步骤或者重写这个步骤的时候,不用影响到其他步骤函数。同样,函数化以后,无疑单元测试就变得非常简单了。

3. 编码价值观 ETC

ETC 这种编码的价值观是很多好的编码原则的本质,比如单一职责原则,解耦原则等,他们都体现了 ETC 这种价值观念。能适应使用者的就是好的设计,对于代码而言,就是要拥抱变化,适应变化。因此我们需要信奉 ETC 。价值观念是帮助你在写代码的时候做决定的,他告诉你应该做这个?还是做那个?他帮助你在不同编码方式之间做选择,他甚至应该成为你编码时的一种潜意识,如果你接受这种价值观,那么在编码的时候,请时刻提醒自己,遵循这种价值观。

总结:

  1. 使每个函数处理的事情尽量单一化,尽可能写成纯函数,便于维护及测试。也方便理解。
  2. 重构未动,测试先行
    重构之前一定要要有充分的测试用例,保证不漏掉一个功能,及改错功能。
  3. 梳理好功能点,先找到痛点
    • 例如很多重复,但又不得不写的代码,可以提取成方法。
    • 例如投放系统中很多场景下用到了form表单提交,可以考虑如何简化写法(使用数组进行渲染各个formItem? 数组的结构该怎么定义?)
    • 如何能提高代码的复用性,和可扩展性
    • 需要高质量的技术方案,确定要如何重构
    • 避免出现重构过程中发现其他问题,影响重构进度
  4. 小心求证,为每行代码负责
  5. 创建新的文件用来重构,保证之前功能可用,一步一步替换其中代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/30778.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++核心编程:C++中的引用

C中的引用 引用的基本语法 作用&#xff1a;给变量起别名 语法&#xff1a;数据类型 & 别名 原名 //比如给一个int变量a命名一个别名 b int &b a;b 20; cout<< a << endl;//a 20引用的注意事项 引用必须初始化 int &b;//错误的引用在初始化后&…

Wav2Lip实践

1. 安装 1.1 安装 conda以指定python版本运行环境 下载&#xff1a;Index of /https://repo.anaconda.com/archive/index.html 1.2 如按旧项目基于python3.6版本对话&#xff0c;会有很多包找不到的情况&#xff0c;经摸索后以python3.9构建成功&#xff0c; conda instal…

LT8711HE 是一款高性能的Type-C/DP1.2到HDMI2.0转换器

LT8711HE 1.描述 LT8711HE是一种高性能的Type-C/DP1.2到HDMI2.0转换器&#xff0c;设计用于连接USB Type-C源或DP1.2源到HDMI2.0接收器。LT8711HE集成了一个DP1.2兼容的接收器&#xff0c;和一个HDMI2.0兼容的发射机。此外&#xff0c;还包括两个CC控制器&#xff0c;用于CC通…

单例模式-java实现

介绍 单例模式的意图&#xff1a;保证某个类在系统中有且仅有一个实例。 我们可以看到下面的类图&#xff1a;一般的单例的实现&#xff0c;是属性中保持着一个自己的私有静态实例引用&#xff0c;还有一个私有的构造方法&#xff0c;然后再开放一个静态的获取实例的方法给外界…

【手撕C语言】多线程

(꒪ꇴ꒪ )&#xff0c;Hello我是祐言QAQ我的博客主页&#xff1a;C/C语言,Linux基础,ARM开发板&#xff0c;软件配置等领域博主&#x1f30d;快上&#x1f698;&#xff0c;一起学习&#xff0c;让我们成为一个强大的攻城狮&#xff01;送给自己和读者的一句鸡汤&#x1f914;&…

python 基础

1.Python 1.1 环境搭建&#xff1a;官网下载python&#xff0c;编译器&#xff1a;pycharm或jupyter 1.2 变量 &#xff08;1&#xff09;语法&#xff1a;变量名变量 &#xff08;2&#xff09;标识符命名规则&#xff1a;由数字、字母、下划线组成&#xff1b;不能以数字…

【数据结构与算法——TypeScript】数组、栈、队列、链表

【数据结构与算法——TypeScript】 算法(Algorithm)的认识 解决问题的过程中&#xff0c;不仅仅 数据的存储方式会影响效率&#xff0c;算法的优劣也会影响效率 什么是算法&#xff1f; 定义&#xff1a; &#x1f7e2; 一个有限指令集&#xff0c;每条指令的描述不依赖于言语…

UE5、CesiumForUnreal接入WMTS格式地图瓦片,如ArcGIS、Mapbox、天地图

文章目录 1.实现目标2.实现过程2.1 WMTS与TMS2.2 cesium-native改造2.3 CesiumForUnreal插件改造2.4 WMTS瓦片加载测试2.5 EPSG:3857与43263.参考资料1.实现目标 通过改造cesium-native和CesiumForUnreal插件,参考tms的栅格瓦片地图加载逻辑,实现在UE5中通过CesiumForUnreal…

后端进阶之路——万字总结Spring Security与数据库集成实践(五)

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 「推荐专栏」&#xff1a; ★java一站式服务 ★ ★前端炫酷代码分享 ★ ★ uniapp-从构建到提升★ ★ 从0到英雄&#xff0c;vue成神之路★ ★ 解决算法&#xff0c;一个专栏就够了★ ★ 架…

数据结构——空间复杂度

3.空间复杂度 空间复杂度也是一个数学表达式&#xff0c;是对一个算法在运行过程中临时占用存储空间大小的量度 。 空间复杂度不是程序占用了多少bytes的空间&#xff0c;因为这个也没太大意义&#xff0c;所以空间复杂度算的是变量的个数。 空间复杂度计算规则基本跟实践复杂…

springboot中@Async的简单用法

springboot中Async的简单用法 文章目录 springboot中Async的简单用法开启配置Async的使用无返回值调用带返回值的调用 开启配置 在配置文件或者入口文件上新增注解: EnableAsync即可 Async的使用 对应需要异步调用的方法上添加Async注解即可 无返回值调用 controller代码 …

四项代表厂商,Kyligence 入选 Gartner 数据及人工智能相关领域多项报告

近日&#xff0c;全球权威的技术研究与咨询公司 Gartner 发布了《2023 年中国数据、分析及人工智能技术成熟度曲线》、《2023 年分析与商业智能技术成熟度曲线报告》、《2023 年数据管理技术成熟度曲线报告》&#xff0c;Kyligence 分别入选这三项报告的指标平台 Metrics Store…

[保研/考研机试] KY187 二进制数 北京邮电大学复试上机题 C++实现

描述 大家都知道&#xff0c;数据在计算机里中存储是以二进制的形式存储的。 有一天&#xff0c;小明学了C语言之后&#xff0c;他想知道一个类型为unsigned int 类型的数字&#xff0c;存储在计算机中的二进制串是什么样子的。 你能帮帮小明吗&#xff1f;并且&#xff0c;小…

《兴森大求真》重磅来袭!先进电子电路可靠性大揭秘

兴森实验室&#xff0c;让可靠看得见 前言介绍 芯片性能不断增强、先进封装不断演进&#xff0c;导致封装基板信号互连的IO数量和密度不断增加、PCB的层数增加、孔间距减小、厚径比提升&#xff0c;可靠性的挑战正在加剧。 电路板作为各种电子元器件的载体和电路信号传输的枢…

ROS入门-常见的rostopic命令及其用法的示例

目录 常见的rostopic命令及其用法的示例 1. 列出所有可用的话题&#xff1a; 2. 获取话题详细信息&#xff1a; 3. 实时显示话题消息内容&#xff1a; 4. 发布消息到话题&#xff1a; 5. 发布随机消息到话题&#xff1a; 6. 查看话题消息类型&#xff1a; 7. 查看话题消…

UDP简介

UDP 1. UDP格式2. UDP特点3. 差错检验 1. UDP格式 16位UDP长度&#xff0c;表示整个数据报&#xff08;UDP首部UDP数据&#xff09;的最大长度&#xff1b; 如果校验和出错&#xff0c;就会直接丢弃; 2. UDP特点 无连接: 知道对端的IP和端口号就直接进行传输&#xff0c;不需…

idea使用protobuf

本文参考&#xff1a;https://blog.csdn.net/m0_37695902/article/details/129438549 再次感谢分享 什么是 protobuf &#xff1f; Protocal Buffers(简称protobuf)是谷歌的一项技术&#xff0c;用于结构化的数据序列化、反序列化。 由于protobuf是跨语言的&#xff0c;所以用…

xxx酒业有限责任公司突发环境事件应急预案WORD

导读&#xff1a;原文《xxx酒业有限责任公司突发环境事件应急预案word》&#xff08;获取来源见文尾&#xff09;&#xff0c;本文精选其中精华及架构部分&#xff0c;逻辑清晰、内容完整&#xff0c;为快速形成售前方案提供参考。 目 录 1 总则 1.1 编制目的 1.2 编制…

【vue3】基础知识点-pinia

学习vue3&#xff0c;都会从基础知识点学起。了解setup函数&#xff0c;ref&#xff0c;recative&#xff0c;watch、computed、pinia等如何使用 今天说vue3组合式api&#xff0c;pinia 戳这里&#xff0c;跳转pinia中文文档 官网的基础示例中提供了三种写法 1、选择式api&a…

python进阶

目录 Json数据格式 前言 JSON格式 python数据和Json数据的相互转化 网络编程 套接字 socket服务端编程步骤 socket客户端编程步骤 python操作mysql数据库 查询并接收数据 数据插入 Json数据格式 前言 JSON是一种轻量级的数据交换格式&#xff0c;可以按照JSON指定…