5w原则
.1.(when)什么时候应该拆分组件
当出现:
- 代码量大,所有内容集中在一起
- 相同组件无法复用
- 业务开发分工不明确,开发人员要关心非业务的代码
- 改代码时,可能会影响其他业务,牵一发动全身(耦合)
- 任何一个操作都导致整个应用重新render
目的:提高可读性、可维护性,解耦
2.(how)如何拆分组件
把相关联的东西放一起(按功能、业务)
- 横向(按业务、功能模块划分)
- 纵向(应用、系统层级划分)
经典案例:
不拆分
登录组件处理了2件事情:
- 渲染登录表单
- 记录用户输入和登录状态,向后台发送登录请求
class Login extends Component {
constructor(props) {
super(props)
this.state = {
account: '',
password: '',
status: 'init',
}
}
handleAccountChange(e) {
this.setState({account: e.target.value})
}
handlePasswordChange(e) {
this.setState({password: e.target.value})
}
handleLoginClick() {
this.setState({ status: 'ing' })
request('/login', {
params: {
account: this.state.account,
password: this.state.password,
}
}).then(() => {
this.setState({status: 'succ'})
}).catch(() => {
this.setState({status: 'fail'})
})
}
render() {
return (
<div>
<input
placeholder="账号"
value={this.state.account}
onChange={(...args) => this.handleAccountChange(...args)}
/>
<input
placeholder="密码"
value={this.state.password}
onChange={(...args) => this.handlePasswordChange(...args)}
/>
<button onClick={() => this.handleLoginClick()}>登录</button>
</div>
)
}
}
拆分后
容器组件负责实现登录功能,展示组件负责渲染内容。
如果要实现另一套登陆组件时,可直接复用容器组件,只需要实现新的展示组件即可。
// 业务组件 可复用性比较高
function withLogin(config) {
const { mapStateToProps, mapDispatchToProps } = config
return (Comp) => {
class Container extends Component {
constructor(props) {
super(props)
this.state = {
account: '',
password: '',
status: 'init',
}
}
handleAccountChange = (e) => {
this.setState({account: e.target.value})
}
handlePasswordChange = (e) => {
this.setState({password: e.target.value})
}
handleLoginClick = () => {
this.setState({ status: 'ing' })
request('/login', {
params: {
account: this.state.account,
password: this.state.password,
}
}).then(() => {
this.setState({status: 'succ'})
}).catch(() => {
this.setState({status: 'fail'})
})
}
render() {
const propsFromState = mapStateToProps(this.state, this.props)
const propsFromDispatch = mapDispatchToProps({
onAccountChange: this.handleAccountChange,
onPasswordChange: this.handlePasswordChange,
onSubmit: this.handleLoginClick,
}, this.props)
return (
<Comp
{...this.props}
{...propsFromState}
{...propsFromDispatch}
/>
)
}
}
return LoginContainer
}
}
// 展示组件
class Login extends Component {
render() {
const { account, password, onAccountChange, onPasswordChange, onSubmit }
return (
<div>
<input
placeholder="账号"
value={account}
onChange={(...args) => onAccountChange(...args)}
/>
<input
placeholder="密码"
value={password}
onChange={(...args) => onPasswordChange(...args)}
/>
<button onClick={() => onSubmit()}>登录</button>
</div>
)
}
}
// 连接组件
const LoginContainer = withLogin({
mapStateToProps: (state, props) => {
return {
account: state.account,
password: state.password,
}
},
mapDispatchToProps: (dispatch, props) => {
return {
onAccountChange: dispatch.onAccountChange,
onPasswordChange: dispatch.onPasswordChange,
onSubmit: dispatch.Submit,
}
}
})
渲染优化
把UI上相互独立的部分,划分成不同组件,防止渲染时相互影响。最常见的是列表组件。
拆分前
点击一个li, 其他li全都重新渲染
class List extends Component {
state = {
selected: null
}
handleClick(id) {
this.setState({selected: id})
}
render() {
const { items } = this.props
return (
<ul>
{
items.map((item, index) => {
const {text, id} = item
const selected = this.state.selected === id
return (
<li
key={id}
className={selected ? 'selected' : ''}
onClick={() => this.handleClick(id)}
>
<span>{text}</span>
</li>
)
})
}
</ul>
)
}
}
拆分后
子组件使用PureComponent
或memo
,并且click事件回调函数直接使用this.handleClick
,而不是每次都创建新函数。
点击li,最多只会有2个子组件渲染。
// onClick时需要的参数,要传进来
class Item extends PureComponent {
render() {
const { id, text, selected, onClick } = this.props
return (
<li
className={selected ? 'selected' : ''}
onClick={onClick(id)}
>
<span>{text}</span>
</li>
)
}
}
class List extends Component {
state = {
selected: null
}
handleClick(id) {
this.setState({selected: id})
}
render() {
const { items } = this.props
return (
<ul>
{
items.map((item, index) => {
const {text, id} = item
return (
<Item
key={id}
id={id} // 传进去
selected={this.state.selected === id}
text={text}
onClick={this.handleClick}
/>
)
})
}
</ul>
)
}
}