本文简单介绍了如何编写一个 React 组件为了快速演示, 文章中出现的示例代码均用 react-create-app 官方推荐的脚手架快速搭建的项目中完成, react-create-app 传送门
一组件的介绍
Components let you split the UI into independent, reusable pieces, and think about each piece in isolation.
组件可以让你把 UI 切分成独立的可复用的块去单独的考虑和开发
二组件的写法
展示型组件 (Pure Component 函数组件)
展示型组件是用来展示样式的, 他们不绑定任何东西且没有依赖性, 通常被实现为无状态功能组件
- // BlogList.jsimport React from "react";
- export const BlogList = bloglist => (
- <ul>
- {bloglist.map(({ body, author,id }) =>
- <li key={id}>{body}-{author}</li>
- )}
- </ul>
- )
使用 class 定义一个组件
- import React from "react";
- class Welcome extends React.Component {
- render() {
- return <h1>Hello, {this.props.name}</h1>;
- }
- }
三组件的编写
Demo 1
以上面的展示型组件为例子, 在用 react-create-app 生成的目录中创建 components 文件夹用于存放我们的组件 (react-create-app 的使用方法 传送们)
然后在我们的 App.js 里面 引入 BlogList.js
最后在页面中查看
Demo 2
最近用了 Angular Js 和 Vue, 在写表单的时候基于双向数据绑定, 写起来是相当的 happy, 当然, React 的单向数据流也有着自己的优点基于此我们可以对项目中常用的表单控件进行组件的封装, 下面以 checkbox 为列
stap1:
首先我们在 components 文件夹下面创建 Checkbox 文件夹以及 Checkbox.js 文件
stap2:
按照通常的写页面的方式编写 Checkbox.js 文件, 并在 App.js 中引用
- // Checkbox.js
- import React,{Component} from 'react';
- class CheckBox extends Component {
- render() {
- return (
- <label><input type="checkbox" /> 点击我 </label>
- )
- }
- }export default CheckBox;
- // App.js
- import CheckBox from './compoents/CheckTest/CheckTest'
- ....
- return (
- <div className="App">
- .....
- <div>
- <CheckBox></CheckBox>
- </div>
- </div>
- );
这样我们的 Checkbox 组件就被加载到了页面, 但是 label 被我们写死了点击我, 怎么实现动态替换呢?
- // Checkbox.js
- <span>
- {this.props.children !== undefined ? this.props.children : null}
- </span>
- // App.js
- render() {
- return (
- <div className="App">
- ...
- <div>
- <CheckBox > 新的点击我 </CheckBox>
- </div>
- </div>
- );
- }
现在查看, 页面中的点击我就替换成了新的点击我了, 到目前为止, 实现了一个无状态的 Checkbox, 但是在开发中, 我们要获取到 checkbox 的选中状态, 如果有多个 checkbox , 我们还要得到选中的是哪几个 checkbox
- stap3:
- // Checkbox.js
- import React from "react"import "./CheckBox.less"
- class CheckBox extends React.Component {
- constructor(props) {
- super(props)
- this.checkCheckBox = this.checkCheckBox.bind(this);
- this.state = {
- value: props.value || ''
- }
- }
- checkCheckBox() {
- const onChange = this.props.onChange;
- const value = this.props.value;
- if (onChange) {
- onChange(value);
- }
- }
- render() {
- let {value} = this.state;
- return (
- <label>
- <input value={value} type="checkbox" onClick={this.checkCheckBox}/>
- {this.props.children !== undefined ? this.props.children : null}
- </label>
- )
- }}export default CheckBox;
上述代码, 我们的 Checkbox 接收父组件传下来的 value 和 onChange 方法, 在点击的时候, 把组件的 value 值传上去, 然后在父组件中获取
- class App extends Component {
- onChange=(value)=>{
- console.log('当前点击',value)
- }
- render() {
- return (
- <div className="App">
- ...
- <div>
- <CheckBox value='checkbox1' onChange={this.onChange}>checkbox1</CheckBox>
- <CheckBox value='checkbox2' onChange={this.onChange}>checkbox2</CheckBox>
- </div>
- </div>
- );
- }}
这样在每次点击的时候可以在控制台看到
stap4:
现在有个新的需求, 在 checkbox 加载的时候有的 checkbox 是默认选中的, 怎么样在父组件控制选中状态呢
- class CheckBox extends React.Component {
- constructor(props) {
- super(props)
- this.checkCheckBox = this.checkCheckBox.bind(this);
- this.state = {
- is_checked: props.checked || false,
- value: props.value || ''
- }
- }
- render() {
- let {is_checked,value} = this.state;
- return (
- <label>
- <input value={value} type="checkbox" onClick={this.checkCheckBox}
- checked={is_checked}/>
- {this.props.children !== undefined ? this.props.children : null}
- </label>
- )
- }}
这样, 我们的 Checkbox 组件就是受控的了, 也就是说它的选中状态是与父组件传下来的 checked 属性相关连的, 现在在次点击按钮, 发现无论怎么点也选不上了
stap5:
我们的 Checkbox 组件一般都不会单独的加载, 尤其是在选择项很多的业务场景中, checkbox 组件都是分组出现的, 我们希望在点击的时候能在父组件拿到选中的 Checkbox 的数组, 并且改变 checkbox 的选中状态
首先我们再在 components 文件夹下面创建一个 CheckboxGroup 文件夹, 同样在下面新建 CheckboxGroup.js 首先贴出来代码, 然后再来分析
- import React from "react"import "./CheckboxGroup.less"import PropTypes from 'prop-types';
- class CheckboxGroup extends React.Component {
- constructor(props) {
- super(props) this.state = {
- value: props.value || props.defaultValue || [],
- };
- this.toggleOption = this.toggleOption.bind(this);
- }
- componentWillReceiveProps(nextProps) {
- if ('value' in nextProps) {
- this.setState({
- value: nextProps.value || [],
- });
- }
- }
- getChildContext() {
- return {
- checkboxGroup: {
- toggleOption: this.toggleOption,
- value: this.state.value,
- },
- };
- }
- toggleOption(option) {
- const optionIndex = this.state.value.indexOf(option.value);
- const value = [...this.state.value];
- if (optionIndex === -1) {
- value.push(option.value);
- } else {
- value.splice(optionIndex, 1);
- }
- if (! ('value' in this.props)) {
- this.setState({
- value
- });
- }
- const onChange = this.props.onChange;
- if (onChange) {
- onChange(value);
- }
- }
- render() {
- const {
- children,
- className
- } = this.props
- return ( < div className = {
- className
- } > {
- children
- } < /div>
- )
- }}
- CheckboxGroup.childContextTypes = { checkboxGroup: PropTypes.any,};export default CheckboxGroup/
代码分析: 从 render 开始看
- render() {
- const {children, className} = this.props
- return (
- <div className={className}>
- {children}
- </div>
- )
- }
这段代码很熟悉, 和上面的 Checkbox 组件一样, 会加载组件包裹的元素, 也就是说, CheckboxGroup 组件包裹了 Checkbox, 那是怎样接管 Checkbox 的状态的呢?
- getChildContext() {
- return {
- checkboxGroup: {
- toggleOption: this.toggleOption,
- value: this.state.value,
- },
- };
- }
getChildContext 可以传递给自组件属性, 需要主意的是, getChildContext 指定的传递给子组件的属性需要先通过 childContextTypes 来指定, 不然会产生错误
- CheckboxGroup.childContextTypes = {
- checkboxGroup: PropTypes.any,
- };
然后他会 getChildContent 中返回子组件需要的属性, 同时在 componentWillReceiveProps 生命周期中监听 props 的改变从而更新 CheckBox 的 选中状态
同时, CheckBox.js 里面也要做相应的修改
- import React from "react"
- import "./CheckBox.less"
- import PropTypes from 'prop-types';
- import CheckboxGroup from '../CheckboxGroup/CheckboxGroup.js'
- class CheckBox extends React.Component {
- constructor(props) {
- super(props)
- this.checkCheckBox = this.checkCheckBox.bind(this);
- this.state = {
- is_checked: props.checked || false,
- value: props.value || ''
- }
- }
- checkCheckBox() {
- const {checkboxGroup} = this.context;
- if (checkboxGroup) {
- checkboxGroup.toggleOption({label: this.props.children, value: this.props.value})
- } else {
- const onChange = this.props.onChange;
- const value = this.props.value;
- if (onChange) {
- onChange(value);
- }
- }
- }
- render() {
- let {is_checked, value} = this.state;
- const {checkboxGroup} = this.context;
- if (checkboxGroup) {
- is_checked = checkboxGroup.value.indexOf(this.props.value) !== -1;
- }
- return (
- <label>
- <input value={value} type="checkbox" checked={is_checked}
- onClick={this.checkCheckBox}/>
- {this.props.children !== undefined ? this.props.children : null}
- </label>
- )
- }}
- CheckBox.Group = CheckboxGroup;
- CheckBox.contextTypes = {
- checkboxGroup: PropTypes.any,
- }export default CheckBox;
这样, 在点击的时候会先判断 this.context 中是否有 checkboxGroup 属性, 如果有, 就调用 checkboxGroup 属性 中的 toggleOption 方法, 并把当前的 value 传上去, 由 CheckboxGroup 组件去控制, 同时在渲染的时候也会判断, 从而决定选中状态是由谁来决定然后去 App.js 中引用
- import React, { Component } from 'react';
- import logo from './logo.svg';
- import './App.css';
- import CheckBox from './compoents/CheckBox/CheckBox'
- const CheckboxGroup = CheckBox.Group;
- class App extends Component {
- state={
- checkList:[]
- }
- selectCheckBtn=(values)=>{
- console.log(values)
- this.setState({
- checkList:values
- })
- }
- render() {
- const {checkList} = this.state
- return (
- <div className="App">
- <header className="App-header">
- <img src={logo} className="App-logo" alt="logo" />
- <h1 className="App-title">Welcome to React</h1>
- </header>
- <p className="App-intro">
- To get started, edit <code>src/App.js</code> and save to reload.
- </p>
- <CheckboxGroup value={checkList} onChange={this.selectCheckBtn}>
- <CheckBox value={'1'}> 按钮 1</CheckBox>
- <CheckBox value={'2'}> 按钮 2</CheckBox>
- <CheckBox value={'3'}> 按钮 3</CheckBox>
- <CheckBox value={'4'}> 按钮 4</CheckBox>
- </CheckboxGroup>
- </div>
- );
- }}
- export default App;
最后, 打开控制台, 点击按钮
这里, 我们便完成了一个简单的 checkBox 组件
总结
写一个组件很容易, 但是写好一个组件就不是那么容易的事了, React 也有一些现在比较成熟的 UI 组件库, 比如蚂蚁金服的 Antd Design, 可以打开看看里面的源码学习下他们的设计思想
来源: https://juejin.im/post/5a9a30f1518825556533f0b9