Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

recompose 使用方法 #41

Closed
yuxino opened this issue Feb 10, 2018 · 1 comment
Closed

recompose 使用方法 #41

yuxino opened this issue Feb 10, 2018 · 1 comment
Labels
Projects

Comments

@yuxino
Copy link
Owner

yuxino commented Feb 10, 2018

因为公司用到了。所以特地学习一下用法。在此记录一下。

recompose官方对自己的定位如下:

A React utility belt for function components and higher-order components.

一个用来把普通方法和高阶组件组合起来的工具。

Think of it like lodash for React.

可以认为他是React版的lodash。

这里有一个最基础的demo。实现了通过按钮控制count的增减。

const { withStateHandlers } = Recompose;

const Counter = ({ count, increment, decrement }) =>
  <div>
    Count: {count}
    <div>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  </div>;

const App = withStateHandlers(
  {
    count: 0
  },
  {
    increment: ({ count }) => () => ({ count: count + 1 }),
    decrement: ({ count }) => () => ({ count: count - 1 })
  }
)(Counter);

ReactDOM.render(<App />, document.getElementById("container"));

这里完完全全的把组件的状态和功能分离了出来。如果不使用recompose我们可能会这样写。

const Counter = ({ count, increment, decrement }) => (
  <div>
    Count: {count}
    <div>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  </div>
);

class App extends React.Component {
  constructor (props) {
    super(props);
    this.state = { count: 0 }
    this.incr = this.increment.bind(this)
    this.decr = this.decrement.bind(this)
  }
  increment () {
    this.setState({ count: ++this.state.count })
  }
  decrement () {
    this.setState({ count: --this.state.count })
  }
  render () {
    return (<Counter count={ this.state.count } 
                     increment = { this.incr } 
                     decrement = { this.decr } >
           </Counter>)
  }
}

但是写成这样子不得不说实在有点麻烦。源码的话思路差不多

好了 有了最基本的用法 我们再看看recompose给我们带来了什么其他的。

除了上面那个withState的例子以外还有一个withReducer的例子。withReducer只是一种Redux-style风格的写法和真正的Redux无关。

const counterReducer = (count, action) => {
  switch (action.type) {
  case INCREMENT:
    return count + 1
  case DECREMENT:
    return count - 1
  default:
    return count
  }
}

const enhance = withReducer('counter', 'dispatch', counterReducer, 0)
const Counter = enhance(({ counter, dispatch }) =>
  <div>
    Count: {counter}
    <button onClick={() => dispatch({ type: INCREMENT })}>Increment</button>
    <button onClick={() => dispatch({ type: DECREMENT })}>Decrement</button>
  </div>
)

这两种写法你喜欢哪一种取决于你。

你可能注意到了enhance这个单词频繁出现。enhance的意思是增强表示被recompose增强过的组件。

recompose 还封装了很多常用的React模式。比如

const enhance = defaultProps({ component: 'button' })
const Button = enhance(componentFromProp('component'))

<Button /> // renders <button>
<Button component={Link} /> // renders <Link />

这样就可以让Button组件随意选择需要用到的类型。默认是button,可以变成其他的标签比如a,li等等。

除此之外还有withContext。让我们看看React官网提供的标准的Context写法。

import PropTypes from 'prop-types';

class Button extends React.Component {
  render() {
    return (
      <button style={{background: this.context.color}}>
        {this.props.children}
      </button>
    );
  }
}

Button.contextTypes = {
  color: PropTypes.string
};

class Message extends React.Component {
  render() {
    return (
      <div>
        {this.props.text} <Button>Delete</Button>
      </div>
    );
  }
}

class MessageList extends React.Component {
  getChildContext() {
    return {color: "purple"};
  }

  render() {
    const children = this.props.messages.map((message) =>
      <Message text={message.text} />
    );
    return <div>{children}</div>;
  }
}

MessageList.childContextTypes = {
  color: PropTypes.string
};

如果使用withContext。把MessageList改成这样子。

class MessageList extends React.Component {
  render() {
    const children = this.props.messages.map((message) =>
      <Message text={message.text} />
    );
    return <div>{children}</div>;
  }
}

然后提供一个storeprovide里面。就会动态生成组件。此时的MessageList不在是写死的了。你甚至可以变成其他的颜色之类的。只要你传了store

const provide = store => withContext({color: PropTypes.string}, () => ({ ...store }))

举个例子

const EnMessageList= () => <MessageList messages={[{text: 'hello'},{text: 'world'}]}></MessageList>
const Purple = provide({ color: "purple" })(EnMessageList)
const Green = provide({ color: "green" })(EnMessageList)

defaultPropscomponentFromProp的源码非常简单。

withContext也非常简单。就是给组件添加getChildContext方法和增加childContextTypes属性用来检测类型。

原组件只接受一个name。但是我们想在name上做一点其他的事情。这时候我们可以用mapProps

class Name extends Component {
  render () {
    return <div>{this.props.name}</div>
  }
}

const FullName = mapProps(({firstName, lastName}) => {
  return { name:  `${firstName} · ${lastName}`}
})(Name)

const Example = () => <FullName firstName="Gavin" lastName="Phang"></FullName>

这样就增强了原组件的表达能力。

接下来是withProps。他可以说是mapProps的增强。后者只接受方法,而withProps支持接受方法和对象。我们可以直接将上面的mapProps替换成withProps。我们看他的源码。做法就是调用了mapProps然后再把input传到组件里。input可以是方法或者对象。

那我们替换一下。

class Name extends Component {
  render () {
    return <div>{this.props.name}</div>
  }
}

const FullName = withProps(({firstName, lastName}) => {
  return { name:  `${firstName} · ${lastName}`}
})(Name)

const Example = () => <FullName firstName="Gavin" lastName="Phang"></FullName>

另一种直接使用对象的写法。

class Name extends Component {
  render () {
    return <div>{this.props.name}</div>
  }
}

const FullName = withProps({ name:  `Gavin · Phang`})(Name)

const Example = () => <FullName></FullName>

效果一样但是第二种写死了。

接下来是withPropsOnChange。我们来看一个场景。这个按钮点击的话state就会改变。然后传递到子组件的props也会变。此时子组件会被重新渲染。

class Name extends Component {
  render() {
    return <div>{this.props.name}</div>;
  }
}

class Change extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.click.bind(this);
    this.state = { name: "abc" };
  }

  click() {
    this.setState({ name: "cba" });
  }

  render() {
    return (
      <div>
        <Name name={this.state.name} />
        <button onClick={this.handleClick}>change</button>
      </div>
    );
  }
}

但是如果我们不想每次都变化只想某个属性变化的时候才重新渲染怎么做呢。答案就是用withPropsOnChangewithPropsOnChange接收的参数如下。

withPropsOnChange(
  shouldMapOrKeys: Array<string> | (props: Object, nextProps: Object) => boolean,
  createProps: (ownerProps: Object) => Object
): HigherOrderComponent

shouldMapOrKeys可以接受数组或者一个返回boolean的方法。createProps参数只能传方法并且返回一个Ojbect。

这里我们做个实验。我们增加FullName组件。

const FullName = withPropsOnChange([""], ({ name }) => {
  return { name };
})(Name);

然后改变原来的Change组件的render函数。

class Change extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.click.bind(this);
    this.state = { name: "abc" };
  }

  click() {
    this.setState({ name: "cba" });
  }

  render() {
    return (
      <div>
        <FullName name={this.state.name} />
        <button onClick={this.handleClick}>change</button>
      </div>
    );
  }
}

此时点击的话 我们会发现FullName不会改变了。如果我们给数组传name

const FullName = withPropsOnChange(["name"], ({ name }) => {
  return { name };
})(Name);

这时候会重新渲染。如果我们传方法的话可以更加自由的控制。决定哪些需要情况需要被重新渲染。

defaultProps这个相当有用。假设有一个默认是白色的按钮。

class Button extends Component {
  render() {
    return (
      <button style={{ background: this.props.color || "#FFFFFF" }}>
        hello
      </button>
    );
  }
}

我们想要一个其他颜色的按钮只需要调用一下defaultProps方法就好了。

const newButton = defaultProps({ color: "#FAC123" })(Button);

flattenProp好像有卵用好像没卵用的一个方法。用起来类似解构。

class Span extends Component {
  render() {
    return (
      <span
        style={{
          background: this.props.color || "#FFFFFF",
          border: `1px solid ${this.props.border}`
        }}
      >
        my background is {this.props.color} , and my border is {this.props.border}
      </span>
    );
  }
}

const enhance = compose(
  withProps({
    style: {
      color: "pink",
      border: "green"
    }
  }),
  flattenProp("style")
);

此时这个enhance的组件可以接收style并解构这个style。但是好像不如直接解构来的方便。

有的时候可以让组件看起来相当清晰。但是没什么卵用感觉

// The `post` prop is an object with title, author, and content fields
const enhance = flattenProp('post')
const Post = enhance(({ title, content, author }) =>
  <article>
    <h1>{title}</h1>
    <h2>By {author.name}</h2>
    <div>{content}</div>
  </article>
)

withStatewithHandle算是比较有用的方法。可以用compose把两个函数组合起来。但是实际上写起来还挺麻烦。看这里。不如用更高阶的把他俩组合在一起的函数withStateHandlers。就是文中一开始提到的那个。

这个方法接收的参数如下

withStateHandlers(
  initialState: Object | (props: Object) => any,
  stateUpdaters: {
    [key: string]: (state:Object, props:Object) => (...payload: any[]) => Object
  }
)

一个简单的使用例子

const Counter = withStateHandlers(
  ({ initialCounter = 0 }) => ({
    counter: initialCounter,
  }),
  {
    incrementOn: ({ counter }) => (value) => ({
      counter: counter + value,
    }),
    decrementOn: ({ counter }) => (value) => ({
      counter: counter - value,
    }),
    resetCounter: (_, { initialCounter = 0 }) => () => ({
      counter: initialCounter,
    }),
  }
)(
  ({ counter, incrementOn, decrementOn, resetCounter }) =>
    <div>
      <p>{ counter }</p>
      <button onClick={() => incrementOn(2)}>Inc</button>
      <button onClick={() => decrementOn(3)}>Dec</button>
      <button onClick={resetCounter}>Reset</button>
    </div>
)

branch结合其他函数蛮有用的。用法如下

branch(
  test: (props: Object) => boolean,
  left: HigherOrderComponent,
  right: ?HigherOrderComponent
): HigherOrderComponent

举个例子

const Green = WrappedComponent => props => {
  return (
    <div style={{ color: "white", background: "#51cf66", padding: 8 }}>
      <WrappedComponent {...props} />
    </div>
  );
};

const Red = WrappedComponent => props => {
  return (
    <div style={{ color: "white", background: "#ff6b6b", padding: 8 }}>
      <WrappedComponent {...props} />
    </div>
  );
};

const Text = _ => <div>Ass We Can</div>;

const BC = branch(
  ({ color }) => {
    return color === "green";
  },
  Green,
  Red
)(Text);

const ba = _ => <BC color={"green"} />;

例子是 如果颜色不是绿色的 那就会选用红色的

@yuxino yuxino added note React and removed note labels Feb 10, 2018
@yuxino
Copy link
Owner Author

yuxino commented Feb 15, 2018

done/

@yuxino yuxino added this to Done in TODO Mar 8, 2018
@yuxino yuxino removed the JavaScript label Apr 18, 2018
@yuxino yuxino closed this as completed Oct 9, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
No open projects
TODO
  
Done
Development

No branches or pull requests

1 participant