React设计思想
U I = f ( d a t a ) X UI = f(data)X UI=f(data)X
- 在React中,界面完全由数据驱动
- 在React中,一切都是组件
- 用户界面就是组件
export default function Button {}
- 组件可以嵌套包装,组成复杂功能
- 组件可以用来实现副作用(ajax、修改dom、埋点等)
- props是组件间通讯的基本方式,还有context,redux,event bus等
定义清晰可维护的接口
设计原则
-
保持接口小,props属性少
-
合理拆分组件,根据数据边界划分组件,充分组合
-
把state往上层组件提取,让下层组件只需要实现为纯函数
-
保持小的更新,细粒度且合理拆分组件,react的diff比对是以组件为单位进行的
…
最佳实践
- 避免render函数,访问同样的props和state,代码依然耦合在一起
- 命名:给回调类型的函数加统一前缀 on开头 (onStart, onSubmit)
- 对参数定义类型约束
import PropTypes from 'prop-types'
const ControlButton=(props)=>{
//todo
}
ControlButton.propTypes={
height: PropTypes.number,
content: PropTypes.string.isRequired
}
组件内部实现
功能正常、代码整洁、高性能
1. 组件代码拆分
每个组件都有自己专属的代码文件
const BaseInfo = (props) => {
};
export default BaseInfo;
代码整洁,易维护,组件互不影响
2. 用解构赋值获取props的属性
const BaseInfo = ({activated, onStart, onPause, onReset, onSplit}) => {
}
简化代码,参数一目了然,默认值
3. class组件,利用属性初始化定义state和成员函数
class SellInfo extends React.Component {
//this.state, this.onClick=this.onClick.bind(this)
state = {
isStarted: false,
}
onClick = () => {
this.setState({
isStarted: true,
});
}
}
好看且性能好,保留this
组件设计模式
- 傻瓜组件和聪明组件
- hoc模式
- render props模式
- 提供者模式
- 组合模式
1. 聪明组件和傻瓜组件
-
别名:
- 有状态组件和无状态组件
- 容器组件和渲染组件
-
优点
- 责任分离, 聪明组件负责管理数据状态,傻瓜组件负责展示数据
- 如果要优化界面,只需要去修改傻瓜组件,如果想改进数据管理和获取,修改聪明组件
2. 聪明组件和傻瓜组件:提升性能
-
class: PureComponent,实现shouldComponentUpdate浅比较
-
hoc:React.memo
-
hooks: useMemo, useCallback
2.1 React.memo
- 默认情况浅层比较,传入第二个参数,可自定义深层比较
function MyComponent(props) {
/* render using props */
}
function areEqual(prevProps, nextProps) {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
}
export default React.memo(MyComponent, areEqual);
2.2 useMemo
- 第二个参数为依赖项,执行回调函数
export default (props = {}) => {
console.log(`--- component re-render ---`);
return useMemo(() => {
console.log(`--- useMemo re-render ---`);
return <div>
{/* <p>step is : {props.step}</p> */}
{/* <p>count is : {props.count}</p> */}
<p>number is : {props.number}</p>
</div>
}, [props.number]);
}
2.3 useCallback
- useCallback 缓存钩子函数,useMemo 缓存返回值
const isEvenNumber = useMemo(() => {
return count % 2 === 0;
}, [count]);
const onClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<div>{count} is {isEvenNumber ? 'even':'odd'} number</div>
<button onClick={onClick}></button>
</div>
);
3. hoc模式
const OrderList = () => {
if (getUserId()) {
return ...; // 显示订单列表
} else {
return null;
}
};
const ProductList = () => {
if (getUserId()) {
return ...; // 显示商品列表
} else {
return null;
}
};
3.1 hoc模式:抽取共同逻辑,强化组件
const withAuth = (Component) => {
//强化操作
const NewComponent = (props) => {
if (getUserId()) {
return <Component {...props} />;
} else {
return null;
}
}
return NewComponent;
};
const OrderList = withAuth((props) => {
return ...; // 显示订单列表
});
3.1 hoc特点
- 高阶组件是一个纯函数,传入组件,返回一个新的组件,包含参数组件。
- 高阶组件不能去修改作为参数的组件,一般把传给自己的 props 转手传递给作为参数的组件
- 命名一般以with开头
- 使用场景
- 权限控制,统一对页面进行权限判断,按不同条件渲染不同内容
- 代码复用,可以将重复的逻辑进行抽象
3.2 hoc优缺点
- 优点
- 抽离和复用逻辑
- 条件渲染,控制渲染等
- 缺点
- 组件多层嵌套, 增加复杂度与理解成本,相同命名的props会覆盖老属性,不清楚props来源
//假如this.props中含有name const newProps = {name: "cqm",value: "testData"} return <ComponentClass {...this.props} {...newProps} />
4. render props 模式
- props作为函数的参数来渲染部分页面
const RenderAll = (props) => {
return(
<React.Fragment>
{props.children(props)}
</React.Fragment>
);
};
<RenderAll>
() => <h1>hello world</h1>}
</RenderAll>
4.1 传递props
const Login = (props) => {
const userName = getUserInfo();
if (userName) {
const allProps = {userName, ...props};
return {props.children(allProps)};
} else {
return null;
}
};
<Login>
{({userInfo}) => <h1>Hello {userName}</h1>}
</Login>
4.2 不局限于children
const Auth= (props) => {
const userName = getUserName();
if (userName) {
const allProps = {userName, ...props};
return {props.login(allProps)};
} else {
retrun {props.nologin(props)}
}
};
<Auth
login={({userName}) => <h1>Hello {userName}</h1>}
nologin={() => <h1>Please login</h1>}
/Auth>
4.3 依赖注入
- 逻辑A依赖于逻辑B和C(A->B、A->C )。依赖注入就是把 B 的逻辑以函数形式传递给 A,A 和 B 达成函数接口一直,逻辑C也可以重用逻辑 A。
- Login 和 Auth相当于逻辑 A,而传递给组件的函数类型 props,就是逻辑 B 和 C
4.4 和高阶组件的比较
- props命名可修改,不存在相互覆盖
- 清楚props来源
- 比高阶组件更加灵活和简单
- 不会出现组件多层嵌套,但存在函数回调形式的多层嵌套
5. 提供者模式
场景:假如组件A和组件X之间隔着多层组件,需要传递数据,一层一层传递太麻烦,对于这种跨组件的数据传递,需要提供者模式
5.1 Provider
- Provider 用于提供 context
//创建context
const ThemeContext = React.createContext();
//提供者
function ThemeProvider(){
const theme = { color:'pink' }
return <ThemeContext.Provider value={ theme } >
<Index />
</ThemeContext.Provider>
}
5.2 Consumer
- Consumer 用于消费 context ,render children 函数中第一个参数就是保存的 context
function ThemeConsumer(props){
return <ThemeContext.Consumer>
{ (theme)=>{ /* render children函数 */
const { color } = theme
return <p style={{color }}>
{props.children}
</p>
} }
</ThemeContext.Consumer>
}
5.2 提供者模式原理
- 通过 Consumer 订阅 context 变化,context 改变,订阅者状态更新
- 使用场景:全局传递状态、保存状态、外层组件项内层组件传值。
6. 组合模式
-
组合模式适合一些容器组件场景,通过外层组件包裹内层组件
-
这种方式在 Vue 中称为 slot 插槽,外层组件可以轻松的获取内层组件的 props 状态,还可以控制内层组件的渲染,
-
组合模式能够直观反映出 父 -> 子组件的包含关系
6.1 组合模式实例
<Tabs onChange={ (type)=> console.log(type) } >
<TabItem name="react" label="react" >React</TabItem>
<TabItem name="vue" label="vue" >Vue</TabItem>
<TabItem name="angular" label="angular" >Angular</TabItem>
</Tabs>
-
Tabs 负责展示和控制对应的 TabItem 。绑定切换 tab 回调方法 onChange。当 tab 切换的时候,执行回调。
-
TabItem 负责展示对应的 tab 项,向 Tabs 传递 props 相关信息。
6.2 组合模式的原理
-
在容器组件中通过props.children获取子组件的children,做进一步处理
-
可以对子组件做类型判断、容器的props混入子组件、子组件传入props的判断控制渲染
-
既然可以混入容器组件的props,进而可以传递方法参数组件通信