0


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。classTestextendsPureComponent{
    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呢constTest=({})=>{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>)}// 这时可以把状态提取至公共状态constuseRequest=(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 };}constTest=({})=>{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  
classAppextendsReact.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同步函数中等),会进行优化,只会触发一次渲染

constDemo4=()=>{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},[])functionhandleAdd(){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}`);// 1
        console.log(`----count---- ${count}`);// 0},2000);}// hook function componentconstaddHandleTimeout2=()=>{
    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};functionreducer(state, action){switch(action.type){case'increment':return{count: state.count +1};case'decrement':return{count: state.count -1};default:thrownewError();}}functionCounter(){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时都会执行 ,而传递一个空数组时,则只会在组件创建时被执/行一次。
副作用函数在任何情况下一定会调用至少一次

constDemo5=()=>{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,值改变不会触发渲染
值修改时不会触发渲染,所以用来保存不希望触发渲染的变量

functionTextInputWithFocusButton(){const inputEl =useRef(null);constonButtonClick=()=>{// `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();};return(<><input ref={inputEl} type="text"/><button onClick={onButtonClick}>Focus the input</button></>);// 也可以直接给ref.current赋值,如下方的例子usePreviousValue,通过ref来保存之前的值}// ref的穿透操作,父级使用子组件中的方法constParent=()=>{const childRef =useRef(null);consthandleClick=()=>{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);functionApp(){return(<ThemeContext.Provider value={themes.dark}><Toolbar /></ThemeContext.Provider>);}functionToolbar(props){return(<div><ThemedButton /></div>);}functionThemedButton(){const theme =useContext(ThemeContext);return(<button
          style={{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方法

constTest=()=><div>test</div>exportdefaultmemo(Test,(prevProps, nextProps)=>{if(prevProps.xxx === nextProps.xxx){returntrue;}returnfalse;})

五. Hooks的规范

1. 只在最顶层使用 Hook

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

2. 只在 React 函数中调用 Hook

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

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

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

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

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

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

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

functionuseEffect(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的特性functionuseDidMount(fn){useEffect(()=>{fn()},[])}

2. useWillUnmount

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

3. useForceUpdate

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

4. usePreviousValue

// 获取上一次render时某个变量的值functionusePreviousValue(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属性functionuseBoolean(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改造constreducer=(state, action)=>{switch(action.type){case'true':returntrue;case'false':returnfalse;case'toggle':return!state;case'set':return action.type;}}const[state, dispatch]=useReducer(reducer,false);

6. useRequest

functionuseRequest(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的操作是在下次执行该副作用之前调用constuseUpdateEffect=(effect, deps)=>{const isMounted =useRef(false);useEffect(()=>{// 第一次执行是在mount阶段,故不返回effect,第二次执行之后才会设置effect,在第三次执行前,会执行effect方法if(!isMounted.current){
      isMounted.current =true;}else{returneffect();}}, deps);};

七. 推荐使用的hooks的库

  • ahooks,有很多hooks可用,而且有一些和antd相关联的hooks,如useAntdTable,基于 useRequest 实现,加载态,分页都可支持
  • hox:hooks中的状态管理器,代码简单,有兴趣的可以去看一看。
// 定义modalimport{ createModel }from'hox';/* 任意一个 custom Hook */functionuseCounter(){const[count, setCount]=useState(0);constdecrement=()=>setCount(count -1);constincrement=()=>setCount(count +1);return{
     count,
    decrement,
    increment
  };}exportdefaultcreateModel(useCounter)// 使用modalimport useCounterModel from"../models/useCounterModel";functionApp(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. 创建新的文件用来重构,保证之前功能可用,一步一步替换其中代码。

本文转载自: https://blog.csdn.net/qq_28992047/article/details/132081532
版权归原作者 只会写Bug的程序员 所有, 如有侵权,请联系我们删除。

“react中hooks分享”的评论:

还没有评论