请别人做网站注意事项,丰城住房和城乡建设部网站,徐州手机网站开发公司电话,尚层装饰一. HOOKS是什么
在计算机程序设计中#xff0c;钩子一词涵盖了一系列技术#xff0c;这些技术用来通过拦截函数调用、消息或在软件组件之间传递的事件来改变或增加操作系统、应用程序或其他软件组件的行为。处理这些被截获的函数调用、事件或消息的代码称为“hook”。 在r…一. 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可以模拟出大部分生命周期但是像 getSnapshotBeforeUpdategetDerivedStateFromError 和 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 (divpnumber: {number}/pbutton 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. useReduceraction 钩子
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})}-/buttonbutton onClick{() dispatch({type: increment})}/button/);
}3. useEffect副作用钩子
使用useEffect可以模拟很多class组件中的生命周期如componentDidMountcomponentDidUpdate componentWillUnmount等 与 componentDidMount、componentDidUpdate 不同的是传给 useEffect 的函数会在浏览器完成布局与绘制之后在一个延迟事件中被调用。这使得它适用于许多常见的副作用场景比如设置订阅和事件处理等情况因为绝大多数操作不应阻塞浏览器对屏幕的更新。 使用方式 第一个参数为副作用函数 副作用函数可以选择返回一个函数会在下一次执行该副作用或组件注销时调用 第二个参数为依赖数组选填参数在依赖变化时会触发副作用函数重新执行 如果依赖数组不传则组件每次render时都会执行 而传递一个空数组时则只会在组件创建时被执/行一次。 副作用函数在任何情况下一定会调用至少一次
const Demo5 () {const [name, setName] useState();useEffect(() {console.log(name:, name)}, [name])return (divpname: {name}/pbutton 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} typetext /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 (divChild 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 (pnumber: {number}/pinput 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 (divThemedButton //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 () divtest/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 (divdiv{count}/divButtononClick{() {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 实现加载态分页都可支持hoxhooks中的状态管理器代码简单有兴趣的可以去看一看。
// 定义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 (divp{counter.count}/pbutton onClick{counter.increment}Increment/button/div);
}八. 如何重构代码
1. 重构的目标
重构的主要目的在于改善既有代码的设计而不是修改缺陷、新增功能等。
重构可以是修改变量名、重新安排目录这样简单的物理重构也可以是抽取子函数、精简冗余设计这样稍许复杂的逻辑重构。但均不改变现有代码的功能。
重构可以将意大利面条式的杂乱代码整理为千层饼式的整洁代码。整洁的代码更加健壮因此便于建立完善的测试防护网。同时新手老人均可放心地修改。
期望重构之后代码逻辑一目了然扩展和修改非常方便出现故障时能迅速定位和修复。前人摔跤过的地方后人不再栽倒前人思考出的成果后人可直接借用。总之高度人性化极大解放人力和脑力。
2. 什么样的代码一看就懂
但凡遇到那种看着逻辑代码一大堆放在一起的就头大后来发现这些代码都犯了一个相同的错误。没有分清楚什么是步骤什么是实现细节。当你把步骤和细节写在一起的时候灾难也就发生了尤其是那种长年累月迭代出来的代码if 遍地。Hooks 是一个做代码拆分的高效工具但是他也非常的灵活业界一直没有比较通用行的编码规范但是我有点不同的观点我觉得他不需要像 Redux 一样的模式化的编码规范因为他就是函数式编程他遵循函数式编程的一般原则函数式编程最重要的是拆分好步骤和实现细节这样的代码就好读好读的代码才是负责任的代码。
到底怎么区分步骤和细节有一个很简单的方法在你梳理需求的时候你用一个流程图把你的需求表示出来这时候的每个节点基本就是步骤因为他不牵扯到具体的实现。解释太多有点啰嗦了相信你肯定懂对吧。步骤和细节分清楚以后对重构也有很大的好处因为每个步骤都是一个函数不会有像 class 中 this 这种全局变量当你需要删除一个步骤或者重写这个步骤的时候不用影响到其他步骤函数。同样函数化以后无疑单元测试就变得非常简单了。
3. 编码价值观 ETC
ETC 这种编码的价值观是很多好的编码原则的本质比如单一职责原则解耦原则等他们都体现了 ETC 这种价值观念。能适应使用者的就是好的设计对于代码而言就是要拥抱变化适应变化。因此我们需要信奉 ETC 。价值观念是帮助你在写代码的时候做决定的他告诉你应该做这个还是做那个他帮助你在不同编码方式之间做选择他甚至应该成为你编码时的一种潜意识如果你接受这种价值观那么在编码的时候请时刻提醒自己遵循这种价值观。
总结
使每个函数处理的事情尽量单一化尽可能写成纯函数便于维护及测试。也方便理解。重构未动测试先行 重构之前一定要要有充分的测试用例保证不漏掉一个功能及改错功能。梳理好功能点先找到痛点 例如很多重复但又不得不写的代码可以提取成方法。例如投放系统中很多场景下用到了form表单提交可以考虑如何简化写法使用数组进行渲染各个formItem? 数组的结构该怎么定义如何能提高代码的复用性和可扩展性需要高质量的技术方案确定要如何重构避免出现重构过程中发现其他问题影响重构进度 小心求证为每行代码负责创建新的文件用来重构保证之前功能可用一步一步替换其中代码。