目录
- 前言
- 一、background
- 1.1 需求
- 1.2 example
- 二、实现
- 2.1 example
- 2.2 useToggle实现
- 2.3 设计新的dispatch
- 2.4 调用user的onChange逻辑
- 2.5 final version
- 总结
前言
最近开始学习React,跟着Kent学,有很多干货,这里分享Rect中的一个设计模式Control Props,这个设计方法跟React中的Control Component
文章中完整的示例代码可以查看 这里
一、background
1.1 需求
有时候用户希望能够控制state的改变,比如在Inpt的组件中,用户希望控制input中的value以及value改变的action,onChange;对于DOM组件来说,React已经提供了受控组件
对于自定义组件,也可以设计出受控props,只要用户需要,props的控制权应该可以交给用户
1.2 example
比如,对于自定义的Toggle组件,用户可以控制on以及onChange;如果用户不愿意控制,那么就使用props的default on以及onChange
1
2
3<Toggle on={bothOn} onChange={handleToggleChange} /> <Toggle on={bothOn} onChange={handleToggleChange} />
二、实现
2.1 example
这里会借用之前的Toggle组件的例子,向外提供control props的功能
2.2 useToggle实现
useToggle主要是给组件调用的props的,比如用户新建Toggle组件,那么组件需要用的props统一从useToggle中获得
首先不妨先设计useToggle可以接受的参数,其中on以及inChange都是需要做成受控props的
1
2
3
4
5
6
7function useToggle({ initialOn = false, reducer = toggleReducer, onChange, on: controlledOn } = {}) {
由于Toggle向外会提供两个方法,toggle以及reset,在user么有修改情况之下,使用dispatch方法就行了
1
2
3const toggle = () => dispatch({type: actionTypes.toggle}) const reset = () => dispatch({type: actionTypes.reset, initialState})
但是如果需要提供受控props,那么如果user提供的话,就应该使用user提供的
这里不妨设想user控制props情景如下,user控制on,提供了onChange的方法
1
2
3
4
5
6
7
8
9<Toggle on={bothOn} onChange={handleToggleChange} /> function handleToggleChange(state, action) { if (action.type === actionTypes.toggle && timesClicked > 4) { return } setBothOn(state.on) setTimesClicked(c => c + 1) }
也就是说,对于useToggle的dispatch来说需要处理两种情况
- user没有控制props,使用dispatch
- user控制props,调用user控制的方法
2.3 设计新的dispatch
根据上面的要求,不妨设计新的dispatch
1
2
3
4
5
6
7
8
9function dispatchWithOnChange(action) { if (!onIsControlled) { dispatch(action) } onChange?.(reducer({...state, on}, action), action) } const toggle = () => dispatchWithOnChange({type: actionTypes.toggle}) const reset = () => dispatchWithOnChange({type: actionTypes.reset, initialState})
2.4 调用user的onChange逻辑
这里主要讲一下,调用user方法的逻辑
1
2onChange?.(reducer({...state, on}, action), action)
从2.2的API可以知道,user调用的是
1
2onChange = {()=>handleToggleChange(state, action)}
所以需要传新的state以及action,使用reduedr(state, action)可以返回新的state,由于on也是受控props,所以需要单独提取出来,如下
1
2(reducer({...state, on}, action)
2.5 final version
全部的实现(Switch组件以及css)可以到前言中codesandbox链接中参考
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108import * as React from "react"; import { Switch } from "./switch"; const callAll = (...fns) => (...args) => fns.forEach((fn) => fn?.(...args)); const actionTypes = { toggle: "toggle", reset: "reset" }; function toggleReducer(state, { type, initialState }) { switch (type) { case actionTypes.toggle: { return { on: !state.on }; } case actionTypes.reset: { return initialState; } default: { throw new Error(`Unsupported type: ${type}`); } } } function useToggle({ initialOn = false, reducer = toggleReducer, onChange, on: controlledOn } = {}) { const { current: initialState } = React.useRef({ on: initialOn }); const [state, dispatch] = React.useReducer(reducer, initialState); console.log("test on is controller ", controlledOn); const onIsControlled = controlledOn != null; const on = onIsControlled ? controlledOn : state.on; function dispatchWithOnChange(action) { if (!onIsControlled) dispatch(action); onChange?.(reducer({ ...state, on }, action), action); } const toggle = () => dispatchWithOnChange({ type: actionTypes.toggle }); const reset = () => dispatchWithOnChange({ type: actionTypes.reset, initialState }); function getTogglerProps({ onClick, ...props } = {}) { return { "aria-pressed": on, onClick: callAll(onClick, toggle), ...props }; } function getResetterProps({ onClick, ...props } = {}) { return { onClick: callAll(onClick, reset), ...props }; } return { on, reset, toggle, getTogglerProps, getResetterProps }; } function Toggle({ on: controlledOn, onChange }) { const { on, getTogglerProps } = useToggle({ on: controlledOn, onChange }); const props = getTogglerProps({ on }); return <Switch {...props} />; } function App() { const [bothOn, setBothOn] = React.useState(false); const [timesClicked, setTimesClicked] = React.useState(0); function handleToggleChange(state, action) { if (action.type === actionTypes.toggle && timesClicked > 4) { return; } setBothOn(state.on); setTimesClicked((c) => c + 1); } function handleResetClick() { setBothOn(false); setTimesClicked(0); } return ( <div> <div> <Toggle on={bothOn} onChange={handleToggleChange} /> <Toggle on={bothOn} onChange={handleToggleChange} /> </div> {timesClicked > 4 ? ( <div data-testid="notice"> Whoa, you clicked too much! <br /> </div> ) : ( <div data-testid="click-count">Click count: {timesClicked}</div> )} <button onClick={handleResetClick}>Reset</button> <hr /> <div> <div>Uncontrolled Toggle:</div> <Toggle onChange={(...args) => console.info("Uncontrolled Toggle onChange", ...args) } /> </div> </div> ); } export default App; export { Toggle };
总结
受控props可以类比受控组件,目的就是为了让用户有更多的控制权,以方便用户更多自定义的方法,提高了组建的可复用性
最后
以上就是超帅老师最近收集整理的关于React中的设计模式 - 受控属性Control Props前言一、background二、实现总结的全部内容,更多相关React中的设计模式内容请搜索靠谱客的其他文章。
发表评论 取消回复