组件化是近年来前端最重要的一个概念,其实他的本质是聚焦项目中复杂问题的分解。用比较抽象的话来说,就是用乐高积木的思想理解项目,搭建项目。即用无数个不同的小积木(功能简单,结构单一),构建出来一个庞大的精巧的玩具(功能结构都很复杂)。要做到这一点需要将组件理解到最为透彻。
组件划分的艺术
- 组件的划分,从最简单出入手,可以按照层级划分,即每一个项目都可以分为Header,Footer,Content这三大部分,然后Header可以分为Logo,HeaderText,以及HeaderMore,HeaderSearch等几个部分,这是从最简单视觉角度分拆,我们可以把它叫做横向拆分吧。
- 另一种拆分是更加对数据理解透彻之后产生的,就像一些后台项目一样,将要链接数据库的操作分为Model层,在前端即可以理解为将ajax封装起来,封为Model层,将业务逻辑封装为Controller层,在前端就是将对ajax数据的操作分为Controller层,将最终数据的组装,返回分为View层,在前端就是将展现分为View层,这种分层就是典型的MVC架构,只有将这种数据架构理解到深处,才可以真正的上手大型项目。
- 我们这次就聚焦于React下的MVC架构,记录这条路上应该如何走。
最简单的组件组合
- 容器组件和展示组件
- 不知道大家有没有注意观察过淘宝网,它里面有一部分数据是相同的,但是展现形式不同,那这个时候,我们就可以把组件拆开,分为容器组件,和展示组件,容器组件负责拿回来数据,而展示组件就负责展示,在这种情况下,我们可以有不同的展示组件,也可以有不用的容器组件,而我们开发的过程就更像组合积木的过程了
- 容器组件的特点:
- 更关心行为部分,即ajax,以及数据操作
- 负责渲染对应的展示组件
- 定义事件处理
- 用class声明
- 表现组件的特点:
- 更关心视觉展现
- 负责渲染HTML
- 用props从父组件接受数据
- 通常是无状态函数组件/pureComponent
// 关注点分离
// 表现组件
const ShowList = ({data})=>(<div>{data.map(item=>(<h3 key={item.id}>{item.text}</h3>))}</div>)
// 容器组件
class Game extends React.Component{
constructor(){
super();
this.state = {
data: []
}
}
componentDidMount = () => {
axios.get("http://localhost:8081")
.then(res=>res.data)
.then(data=>this.setState(data));
}
render(){
//组装
return (<div className="container">
<ShowList {...this.state}/>
{/* {this.state.data.map(item=><h3 key={item.id}>{item.text}</h3>)} */}
</div>)
}
}
- React中的HOC
- 当我们的一部分业务代码需要复用时,React官方在最开始推荐我们的方法是mixins,但这种方法很容易就导致代码mixins之间相互依赖,变量函数冲突等问题
- 后来就有了高阶组件,react官方也就推荐这种方式作为复用组件逻辑的官方方法,他并不是React官方API。而是由于组件组合特性衍生出来的一种设计模式,这种方法他可以很好的将功能整合,不影响外部环境,并且可以通过多层包装完成minxin的效果,具体效果如下
// 高阶组件,用组件生成组件
const BasicComponent = ({name, width})=>{
return (<h1>我是{name},当前窗口宽度为{width}px</h1>)
};
const withWindowResize = RawComponent=>{
return class NewComponent extends Component {
constructor(props){
super(props);
this.state = {
innerWidth:0
}
}
componentDidMount = () => {
this.setState({
innerWidth: window.innerWidth
});
}
componentWillMount = () => {
window.addEventListener("resize", this.handelResize, false);
}
componentWillUnmount = () => {
window.removeEventListener("resize", this.handelResize, false);
}
handelResize = ()=>{
this.setState({
innerWidth: window.innerWidth
});
}
render(){
return (<RawComponent name="xiaoming" width={this.state.innerWidth}></RawComponent>)
}
}
};
const Game = withWindowResize(BasicComponent);
export default Game;
函数子组件
函数子组件可以理解为通过从props传递参数,然后在props.children定义函数,将参数处理后放入props.children执行这个函数。
class FetchWrapper extends Component {
static propTypes = {
children: PropTypes.func.isRequired,
url: PropTypes.string.isRequired
};
constructor(props) {
super(props);
this.state = {
data: []
}
}
componentDidMount() {
fetch(
this.props.url
)
.then(response => response.json())
.then((data) => this.setState(
{data: data}
))
.catch(console.error);
}
render() {
return this.props.children(this.state.data)
}
}
class App extends Component {
constructor() {
super();
}
render() {
return (
<div>
<FetchWrapper url="https://api.github.com/users/gaearon/gists">
{data => {
return data.map(item => {
return (
<li style={{textAlign: 'left'}} onClick={this.clickList} key={item.id}>
<h5>
{item.id}
</h5>
<p>
{item.description || "no desc"}
</p>
</li>
)
})
}}
</FetchWrapper>
</div>
)
}
}
CONTEXT 跨组件传参
context主要用于组件中的跨层级传参,他的底层就是使用了函数子组件,使用如下
const FirstContext = React.createContext("#");
class Game extends Component {
constructor(){
super();
this.state = {
}
}
render(){
return (
<div>
// 供应商
<FirstContext.Provider value="$">
// 消费者
<FirstContext.Consumer>
{value=>[1,2,3,4].map(i=><h3 key={i}>{i*100}_{value}</h3>)}
</FirstContext.Consumer>
</FirstContext.Provider>
<FirstContext.Consumer>
{value=>[1,2,3,4].map(i=><h3 key={i}>{i*100}_{value}</h3>)}
</FirstContext.Consumer>
</div>
)
}
}