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.rhkb.cn/news/80154.html

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

相关文章

Spring Boot数据访问基础知识与JDBC简单实现

目录 Spring Boot数据访问基础知识 Spring Data ORM JDBC JPA JDBC简单实现 步骤1&#xff1a;新建Maven项目&#xff0c;添加依赖 步骤2&#xff1a;配置数据源—让程序可以访问到 步骤3&#xff1a;配置数据源—让IDEA可以访问到 步骤4&#xff1a;添加数据库和表 …

人类与机器的分类不同

分类能力也是智能的重要标识之一。通过分类&#xff0c;我们可以将事物或概念进行归类和组织&#xff0c;从而更好地理解和处理信息。分类在人类认知和智能发展中起到了重要的作用&#xff0c;它有助于我们对世界进行认知、记忆、推理和决策。在机器智能领域&#xff0c;分类同…

在WebStorm中通过live-server插件搭建Ajax运行环境

1.下载node.js 官网: https://nodejs.cn/download/ 2.配置Node.js的HTTPS 使用淘宝的镜像&#xff1a; npm config set registry https://registry.npm.taobao.org 也可以使用cnpm npm install -g cnpm --registryhttps://registry.npm.taobao.org 配置之后可以验证是否成…

Windows安装子系统Linux

Windows安装子系统(Linux ubuntu&#xff09; 安装条件步骤1.安装WSL命令2.设置Linux用户名和密码3.写个简单的.c程序看看4.如何互传文件 安装条件 Windows 10版本2004及更高的版本才能安装。 步骤 1.安装WSL命令 我们可以使用WSL来安装子系统 Linux ubuntu(默认是这个)。 …

Docker网络模式详解

目录 Docker网络模式 一、Host模式 二、container模式 三、none模式 四、bridge模式 五、Overlay模式 Docker网络模式 安装Docker时会自动创建3个网络&#xff0c;可以使用docker network ls命令列出这些网络。 [rootdocker ~]# docker network ls 我们在使用docker run…

进程、线程、协程

目录 进程、线程、协程的概念 进程、线程、协程的上下文切换 使用协程的注意事项 协程与线程、进程的区别 进程、线程、协程的概念 进程&#xff1a; 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。每个进程…

【雕爷学编程】Arduino动手做(195)---HT16k33 矩阵 8*8点阵屏模块5

37款传感器与模块的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&#x…

HikariDataSource类的作用和使用

HikariDataSource是一个Java数据库连接池的实现&#xff0c;它属于HikariCP连接池库。连接池是一个用于管理数据库连接的工具&#xff0c;它可以帮助优化数据库连接的创建和销毁过程&#xff0c;提高数据库操作的性能和效率。 HikariDataSource类的作用是创建和管理数据库连接…

企业服务器器中了360后缀勒索病毒怎么解决,勒索病毒解密数据恢复

随着网络威胁的增加&#xff0c;企业服务器成为黑客攻击的目标之一。近期&#xff0c;上海某知名律师事务所的数据库遭到了360后缀的勒索病毒攻击&#xff0c;导致企业服务器内的数据库被360后缀勒索病毒加密。许多重要的数据被锁定无法正常读取&#xff0c;严重影响了企业的正…

运维作业—5

一.基于 CentOS 7 构建 LVS-DR 群集 1.配置LVS 2.第一台real server&#xff08;192.168.100.139:80&#xff09; 手工在RS端绑定VIP 手工在RS端抑制ARP响应 3.第二台real server&#xff08;192.168.100.140:80&#xff09; 安装arptables并启动 使用arptables实现抑制 测试…

通向架构师的道路之weblogic与apache的整合与调优

一、BEAWeblogic的历史 BEA WebLogic是用于开发、集成、部署和管理大型分布式Web应用、 网络应用和数据库应 用的Java应用服务器。将Java的动态功能和Java Enterprise标准的安全性引入大型网络应用的 开发、集成、部署和管理之中。 BEA WebLogic Server拥有处理关键Web应…

webpack性能优化

文章目录 1. 性能优化-分包2. 动态导入3. 自定义分包4. Prefetch和Preload5. CDN加载配置6. CSS的提取7. terser压缩7.1 Terser在webpack中配置7.2 css压缩 8. Tree Shaking 消除未使用的代码8.1 usedExports 配置8.2 sideEffects配置8.3 CSS实现Tree Shaking 9. Scope Hoistin…

Qgis核密度分析

不建议使用Qgis进行核密度分析&#xff0c;建议使用arcgis&#xff0c;arcgis更简单。 arcgis核密度分析 Qgis核密度分析有两种&#xff1a; 法一:符号化&#xff0c;热图。 缺点&#xff1a;使用不方便&#xff0c;只适合看一下效果。 法二&#xff1a;工具栏搜索&#xff…

网络安全预警分类流程

网络安全预警指南 随着信息技术的广泛应用与快速发展&#xff0c;传统业务与信息系统的融合程度不断加深&#xff0c;网络安全对国家政治、经济、文化、公共服务活动的影响进一步增大。网络安全形势日趋复杂&#xff0c;安全威胁不断变化&#xff0c;利用网络漏洞、恶意程序从…

MySQL的索引使用的数据结构,事务知识

一、索引的数据结构&#x1f338; 索引的数据结构&#xff08;非常重要&#xff09; mysql的索引的数据结构&#xff0c;并非定式&#xff01;&#xff01;&#xff01;取决于MySQL使用哪个存储引擎 数据库这块组织数据使用的数据结构是在硬盘上的。我们平时写的代码是存在内存…

【JAVA】数组

⭐ 作者&#xff1a;小胡_不糊涂 &#x1f331; 作者主页&#xff1a;小胡_不糊涂的个人主页 &#x1f4c0; 收录专栏&#xff1a;浅谈Java &#x1f496; 持续更文&#xff0c;关注博主少走弯路&#xff0c;谢谢大家支持 &#x1f496; 数组 1. 数组的基本概念1.1 为什么要使用…

SCT82A30DHKR_5.5V-100V Vin同步降压控制器

SCT82A30是一款100V电压模式控制同步降压控制器&#xff0c;具有线路前馈。40ns受控高压侧MOSFET的最小导通时间支持高转换比&#xff0c;实现从48V输入到低压轨的直接降压转换&#xff0c;降低了系统复杂性和解决方案成本。如果需要&#xff0c;在低至6V的输入电压下降期间&am…

策略模式(Strategy)

策略模式是一种行为设计模式&#xff0c;就是定义一系列算法&#xff0c;然后将每一个算法封装起来&#xff0c;并使它们可相互替换。本模式通过定义一组可相互替换的算法&#xff0c;实现将算法独立于使用它的用户而变化。 Strategy is a behavioral design pattern that def…

解释器模式(Interpreter)

解释器模式是一种行为设计模式&#xff0c;可以解释语言的语法或表达式。给定一个语言&#xff0c;定义它的文法的一种表示&#xff0c;然后定义一个解释器&#xff0c;使用该文法来解释语言中的句子。解释器模式提供了评估语言的语法或表达式的方式。 Interpreter is a behav…

基于短信宝API零代码实现短信自动化业务

场景描述&#xff1a; 基于短信宝开放的API能力&#xff0c;实现在特定事件&#xff08;如天气预警&#xff09;或定时自动发送短信&#xff08;本文以定时群发短信为例&#xff09;。通过Aboter平台如何实现呢&#xff1f; 使用方法&#xff1a; 首先创建一个IPaaS流程&…