HOC
高阶组件(HOC),个人认为其实就是将已写好的组件外层再包裹一层,进而可以达成以下操作:
- 代理props
- 反向继承(Inheritance Inversion)
属性代理
const HOC = (WrappedComponent) =>
class WrapperComponent extends React.Component{
render(){
const newProps = {
newName: 'newName'
}
return(
<WrappedComponent {...props} {...newProps} />
)
}
}
class WrappedComponent extends React.Component{
render(){
return(
<div>WrappedComponent, {
this.props.name
}, {
this.props.newName
}<div/>)
}
}
export default HOC(WrappedComponent)
import NewComponent from '../WrappedComponent'
class Index extends React.Component{
render(){
return (
<div>
<NewComponent name="name"/>
</div>
)
}
}
调用后实际效果为被包裹的组件可以获得父组件的数据以及添加新的数据,除了以上操作外,还可以对props中的数据进行删改查过滤等操作,但是要注意一旦改变数据意味着其他开发人员可能不了解此处变化,发生其他连带bug
抽象state
一个比较常见的例子就是从非受控性组件转变为受控性组件:
class WrappedComponent extends React.Component{
render(){
return(
<input name="name" {...this.props} />
)
}
}
const HOC = (WrappedComponent) =>{
class WrapperComponent extends React.Component{
state = {
value: '123'
}
onValueChange = (e) => {
this.setState({
value: e.target.value
})
}
render(){
const newProps = {
value: this.state.value,
onChange: this.onValueChange
}
return(
<WrapperComponent {...newProps} {...props}/>
)
}
}
以上例子可以实现将非受控性组件转变为受控性组件。
渲染劫持
渲染劫持是指我们可以有意识地控制WrappedComponent的渲染过程,从而控制渲染控制的结果。例如我们可以根据部分参数去决定是否渲染组件:
const HOC = (WrappedComponent) =>
class extends WrappedComponent {
render() {
if (this.props.isRender) {
return super.render();
} else {
return null;
}
}
}
甚至我们可以修改修改render的结果:
const HOC = (WrappedComponent) =>
class extends WrappedComponent {
render() {
const elementsTree = super.render();
let newProps = {};
if (elementsTree && elementsTree.type === 'input') {
newProps = {value: 'may the force be with you'};
}
const props = Object.assign({}, elementsTree.props, newProps);
const newElementsTree = React.cloneElement(elementsTree, props, elementsTree.props.children);
return newElementsTree;
}
}
class WrappedComponent extends Component{
render(){
return(
<input value={'Hello World'} />
)
}
}
export default HOC(WrappedComponent) //实际显示的效果是input的值为"may the force be with you"复制代码
上面的例子中我们将WrappedComponent中的input元素value值修改为:may the force be with you。
Redux的connect
想要将redux和react连接起来,需要使用redux-react,该插件提供了两个重要的方法,Provider和Connect。
如果不使用这两个方法也可以:
class App extends Component{
componentWillMount(){
store.subscribe((state)=>this.setState(state))
}
render(){
return <Comp state={this.state}
onIncrease={()=>store.dispatch(actions.increase())}
onDecrease={()=>store.dispatch(actions.decrease())}
/>
}
}
但是这样意味着如果层层包裹的组件很深的话,需要开发者一层一层的将数据传递下去,这样绝对不是redux的最佳开发方式。
const App = () => {
return (
<Provider store={store}>
<Comp/>
</Provider>
)
};
使用Provider包裹整个app,将store传入app内部
class MyComp extends React.Component{
render(){
return(
<div>123</div>
)
}
}
export default connect(...args)(MyComp)
该方法输出的结果就是Comp组件。这种情况下,即便组件包裹的再深,也可以通过connect来实现适时地传递store。
connect方法
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
connect接受四个参数,即mapStateToProps,mapDispatchToProps,mergeProps, options。
mapStateToProps
mapStateToProps(store, ownProps): stateProps
该方法接受两个参数,第一个参数是redux的store,第二个参数是上例中MyComp自己的props,返回的数据为传递给被connect的组件的数据。
const mapStateToProps = (store) => {
return {
count: store.count
}
}
class MyComp extends React.Component{
render(){
return (
<div>{this.props.count}</div>
)
}
}
const Comp = connect(mapStateToProps)(MyComp)
上例最后可以从store中只取出count传递给所需要的组件MyComp。结合此前高阶组件的用法,不难看出实际上connect就是一个高阶组件,它帮助组件来过滤自己所需要的属性,这样就避免了只用redux会出现的一层层调用庞大的store的问题。
const mapStateToProps = (store, ownProps) => {
return {
user: _.find(state.userList, {id: ownProps.userId})
}
}
class MyComp extends Component {
static PropTypes = {
userId: PropTypes.string.isRequired,
user: PropTypes.object
};
render(){
return <div>用户名:{this.props.user.name}</div>
}
}
const Comp = connect(mapStateToProps)(MyComp);
上例中会发现mapStateToProps方法有第二个参数ownProps,如果某些情况下只需要或许某个用户ID的某项数据,可以从当前组件自己的props中拿到用户id,以此为基础查询相应数据即可。
当 state 变化,或者 ownProps 变化的时候,mapStateToProps 都会被调用,计算出一个新的 stateProps,(在与 ownProps merge 后)更新给 MyComp。
这就是将 Redux store 中的数据连接到组件的基本方式。
mapDispatchToProps
该方法的功能主要是将action作为props绑定到MyComp上。
const mapDispatchToProps = (dispatch, ownProps) => {
return {
increase: (...args) => dispatch(actions.increase(...args)),
decrease: (...args) => dispatch(actions.decrease(...args))
}
}
class MyComp extends Component{
render(){
const { count, increase, decrease } = this. props;
return(
<div>
<div>{count}</div>
<button onClick={increase}>increase</button>
<button onClick={decrease}>decrease</button>
</div>)
}
}
const Comp = connect(mapStateToProps, mapDispatchToProps)(MyComp)
mergeProps
不管是stateProps还是dispatchProps,都需要和ownProps merge之后才会赋值给MyComp。connect的第三个参数就是用来做这件事儿。通常来说可以不传这个参数,connect或默认使用Object.assign替代该方法。