terrierscript
7/4/2018 - 10:40 AM

ReactでHoCsの代わりとしてFunction as Child Componentsを利用して型もつける ref: https://qiita.com/terrierscript/items/03df4d522cf9cf431021

ReactでHoCsの代わりとしてFunction as Child Componentsを利用して型もつける ref: https://qiita.com/terrierscript/items/03df4d522cf9cf431021


// ネストしまくるのを回避したいので、Containerのとこだけ切り出して子に渡す
const MixedContainer = ({ children }) => {
  return (
    <AgeStateContainer>
      {ageContainerProps => (
        <BodyStateContainer>
          {bodyStateContainerProps => {
            return children({
              ...ageContainerProps,
              ...bodyStateContainerProps
            });
          }}
        </BodyStateContainer>
      )}
    </AgeStateContainer>
  );
};

class App extends Component {
  render() {
    return (
      <div className="App">
        <h1>Health tool</h1>
        <MixedContainer>
          {({ isYoung, handleWeight, handleHeight, height, weigth  }) => (
            <div>
              {/* 今までと同じなので入力らへんは省略 */}
              <div>
                {isYoung ? (
                  <ForYoungHealthResult height={height}, weight={weigth}/>
                ) : (
                  <ForAdultHealthResult height={height}, weight={weigth}/>                  
                )}
              </div>
            </div>
          )}
        </MixedContainer>
      </div>
    );
  }
}

class AgeStateContainer extends Component {
  state = {
    age: 20
  };
  isYoung() {
    return this.state.age < 16;
  }
  handleChange = e => {
    this.setState({ age: e.target.value });
  };
  render() {
    return (
      <div>
        Age <input value={this.state.age} onChange={this.handleChange} />
        {/*子に返すのはisYoungだけ*/}
        {this.props.children({ isYoung: this.isYoung() })}
      </div>
    );
  }
}

class App extends Component<{},{}> {
  render() {
    return (
      <BodyStateContainer>
        {params => (
          <div className="App">
            <h1>Health tool</h1>
            <div>
              <h2>Input</h2>
              <InputForm {...params} />
            </div>
          </div>
        )}
      </BodyStateContainer>
    );
  }
}

// spread operatorで受け取るのでChildrenPropsをそのまま流用
const InputForm: React.SFC<BodyChildrenProps> = ({
  weight,
  height,
  handleWeight,
  handleHeight
}) => {
  return (
     ...
  );
};

type BodyState = {
  weight: number;
  height: number;
};
type Handlers = {
  handleHeight: React.ChangeEventHandler;
  handleWeight: React.ChangeEventHandler;
  // もうちょっと雑にするなら (e: any) => void;

};
type BodyChildrenProps = Handlers & BodyState;
type BodyStateProps = {
  children: (props: BodyChildrenProps) => React.ReactNode;
  // もうちょっと雑にするなら  Function とか (props: any) => ReactNode とか
};


class BodyStateContainer extends Component<BodyStateProps, BodyState> {
   : 
  render() {
    return this.props.children({
     :
     :
    })
  }
}
class App extends Component {
  render() {
    return (
      <BodyStateContainer>
        {({ handleWeight, handleHeight, ...rest }) => (
          <div className="App">
            <h1>Health tool</h1>
            <div>
              <h2>Input</h2>
              <InputForm
                {...rest}
                onChangeHeight={handleHeight}
                onChangeWeight={handleWeight}
              />
            </div>
            <div>
              <h2>BMI Result</h2>
              <BMIResult {...rest} />
            </div>
          </div>
        )}
      </BodyStateContainer>
    );
  }
}


//InputFormとBMIResultは上記と同じなので省略する

class BodyStateContainer extends Component {
  state = {
    weight: 0,
    height: 0
  };
  handleWeight = e => {
    this.setState({
      weight: e.target.value
    });
  };
  handleHeight = e => {
    this.setState({
      height: e.target.value
    });
  };
  render() {
    return this.props.children({
      ...this.state,
      handleHeight: this.handleHeight,
      handleWeight: this.handleWeight
    });
  }
}

const InputForm = ({ weight, height, onChangeWeight, onChangeHeight }) => {
  return (
    <div>
      <div>
        Height:
        <input value={height} type="number" onChange={onChangeHeight} />
        cm
      </div>,
      <div>
        Weight:
        <input value={weight} type="number" onChange={onChangeWeight} />
        kg
      </div>
    </div>
  );
};

class BMIResult extends Component {
  compute() {
    const { weight, height } = this.props;
    const meterHeight = height / 100;
    return weight / (meterHeight * meterHeight);
  }
  render() {
    const result = this.compute();
    return <div>BMI: {result}</div>;
  }
}

class App extends Component {
  state = {
    weight: 0,
    height: 0
  };
  handleWeight = e => {
    this.setState({
      weight: e.target.value
    });
  };
  handleHeight = e => {
    this.setState({
      height: e.target.value
    });
  };
  render() {
    return (
      <div className="App">
        <h1>BMI tool</h1>
        <div>
          <h2>Input</h2>
          <InputForm
            {...this.state}
            onChangeHeight={this.handleHeight}
            onChangeWeight={this.handleWeight}
          />
        </div>
        <div>
          <h2>Result</h2>
          <BMIResult {...this.state} />
        </div>
      </div>
    );
  }
}
class App extends Component {
  state = {
    weight: 0,
    height: 0
  };
  computeBMI(weight, height) {
    const meterHeight = height / 100;
    return weight / (meterHeight * meterHeight);
  }
  handleWeight = e => {
    this.setState({
      weight: e.target.value
    });
  };
  handleHeight = e => {
    this.setState({
      height: e.target.value
    });
  };
  render() {
    const { weight, height } = this.state;
    const bmi = this.computeBMI(weight, height);
    return (
      <div className="App">
        <h1>BMI tool</h1>
        <div>
          <h2>Input</h2>
          <div>
            Height:
            <input value={height} type="number" onChange={this.handleHeight} />
            cm
          </div>
          <div>
            Weight:
            <input value={weight} type="number" onChange={this.handleWeight} />
            kg
          </div>
        </div>
        <div>
          <h2>Result</h2>
          <div>BMI: {bmi}</div>
        </div>
      </div>
    );
  }
}
const ForAdultHealthResult = rest => {
  return (
    <div>
      <h2>BMI Result</h2>
      <ComputeBMI {...rest}>
        {({ result }) => {
          <div>Your BMI: {result}</div>;
        }}
      </ComputeBMI>
    </div>
  );
};

const ComputeBMI = ({ weight, height, children }) => {
  const meterHeight = height / 100;
  const result = weight / (meterHeight * meterHeight);
  return children({ result });
};
type AgeContainerChildrenProps = {
  isYoung: boolean;
};
type AgeContainerProps = {
  children: (props: AgeContainerChildProps) => React.ReactNode;
};

type MixedChildrenProps = BodyChildrenProps & AgeContainerChildrenProps;
type MixedContaienrProps = {
  children: (props: MixedChildrenProps) => React.ReactNode;
};

const WithStaticValue = ({ children }) => {
  return children({ value: 1234 })
}

const MyItem = ({ value }) => {
  return <div>{value}</div>
}

const WrappedMyItem = () => {
  return (
    <WithStaticValue>
      {({ value }) => (
        <MyItem value={value} />
      )}
    </WithStaticValue>
  )
};
const withStaticValue = (WrappedComponent) => {
  class HOC extends React.Component {
    render() {
      return (
        <WrappedComponent
          value={1234}
        />
      );
    }
  }    
  return HOC;
};

const MyItem = ({value}) => {
  return <div>{value}</div>
}

const WrappedMyItem = withStaticValue(MyItem)