前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React 组件化开发(二):最新组件api

React 组件化开发(二):最新组件api

作者头像
一粒小麦
发布2019-07-18 17:55:36
2.3K0
发布2019-07-18 17:55:36
举报
文章被收录于专栏:一Li小麦一Li小麦

学习的过程,就是把已经实现的功能反复地,变着花样地重构,直到找到最合适的点。

如果连这点觉悟都没有,那就不是一个合格的程序员。而雇主的本质是逐利,最忌讳的是重构,这个问题可以请高水平的工程师来得到缓解,但不可能彻底解决。

本文知识要点

  • Hook
  • 高阶组件
  • 组件通信
  • 上下文
  • React.cloneElement

Hook

文档地址:https://zh-hans.reactjs.org/docs/hooks-intro.html#___gatsby

hook是16.8版本新增的特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

它具有如下特点:

  • 在无需修改状态的情况下,复用状态逻辑。
  • 将相关联的部分拆分为更小的函数,复杂组件将更容易理解。
  • 更简洁,更易理解。
状态钩子 State Hook

函数型组件可以使用状态:

代码语言:javascript
复制
function Example() {
    // 声明一个新的叫做 “count” 的 state 变量,
    // 数组第二个值是变更函数,参数接收的是新状态
    // useState参数是初始值。
    const [count, setCount] = useState(0);

    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={() => setCount(count + 1)}>
                Click me
      </button>
        </div>
    );
}

你点击一次,count就加1.这样就在函数型组件中实现了state。

你可以执行更加复杂的操作:

代码语言:javascript
复制
// 添加水果
function AddFruit({setFruits,fruits}){

    return (
        <input type='text' onKeyUp={(e)=>{
            if(e.keyCode==13){
                setFruits(fruits.concat(e.target.value))
                e.target.value='';
            }
            e.persist()
        }}/>
    )
}

export default function HooksTest() {
    const [fruit, setFruit] = useState("草莓");
    const [fruits, setFruits ] = useState(['草莓','苹果','鸭梨']);

    return (
        <div>
            <p>{fruit === "" ? "请选择喜爱的水果:" : `您的选择是:${fruit}`}</p>
            <AddFruit  setFruits={setFruits} fruits={fruits}></AddFruit>
            <FruitList setFruit={setFruit} fruits={fruits}></FruitList>
        </div>
    );
}

如果用以前的写法,难以想象,用这么短的代码就实现了一个购物车。

副作用钩子 Effect Hook (类似watch)

函数组件执行副作用操作。

副作用是什么鬼?它包括数据获取,设置订阅,手动更改dom等。最典型的就是异步数据获取

基本使用

代码语言:javascript
复制
import { useEffect } from "react";

export default function HooksTest() {
    const [fruit, setFruit] = useState("草莓");
    const [fruits, setFruits] = useState([]);
    // 使用useEffect异步获取数据
    useEffect(() => {
        setTimeout(() => {
            setFruits(['香蕉', '苹果'])
        }, 1000);
    })
    return (
        <div>
            <p>{fruit === "" ? "请选择喜爱的水果:" : `您的选择是:${fruit}`}</p>
            <AddFruit setFruits={setFruits} fruits={fruits}></AddFruit>
            <FruitList setFruit={setFruit} fruits={fruits}></FruitList>
        </div>
    );
}

这样就实现了异步获取数据.

这个和直接settimout有什么区别呢?如果在useEffect中,会发现不断在执行(每隔一秒),如果执行点击,他会越来越快。

代码语言:javascript
复制
export default function HooksTest() {
    const [fruit, setFruit] = useState("草莓");
    const [fruits, setFruits] = useState(['草莓','香蕉']);
    // 使用useEffect异步获取数据
    useEffect(() => {
        document.title=fruit;
    },[fruit])
    return (
        <div>
            <p>{fruit === "" ? "请选择喜爱的水果:" : `您的选择是:${fruit}`}</p>
            <AddFruit setFruits={setFruits} fruits={fruits}></AddFruit>
            <FruitList setFruit={setFruit} fruits={fruits}></FruitList>
        </div>
    );
}

清务必设置依赖选项(在何时执行),如果没有则放一个空数组。

这在设置做分页时非常管用

清除依赖:

代码语言:javascript
复制
useEffect(()=>{...}, [])

useEffect(() => {
    const timer = setInterval(() => {
        console.log('msg');
    }, 1000);
    return function(){
        clearInterval(timer);
} }, []);
useReducer (状态管理lowb实现)

useState的可选项,常用于组件有复杂状态逻辑时,类似于redux中reducer概念。

在redux中,reducer类似vuex中的mutation,接收action,改变state。

代码语言:javascript
复制
import { useReducer } from "react";
// 状态维护reducer
function fruitReducer(state, action) {
    switch (action.type) {
        case "init":
            return action.payload;
        case "add":
            return [...state, action.payload];
        default:
            return state;
    }
}

// 添加水果
function AddFruit({ onAddFruit, fruits }) {

    return (
        <input type='text' onKeyUp={(e) => {
            if (e.keyCode == 13) {
                onAddFruit(e.target.value)
                e.target.value = '';
            }
            e.persist()
        }} />
    )
}

export default function HooksTest() {
    const [fruits, dispatch] = useReducer(fruitReducer, []);
    const [fruit, setFruit] = useState("草莓");
    useEffect(() => {
        setTimeout(() => {
            // 变更状态,提交
            dispatch({ type: "init", payload: ["香蕉", "西瓜"] });
        }, 1000);
    }, []);
    return (
        <div>
            {/*变更状态*/}
            <AddFruit onAddFruit={pname => dispatch({ type: 'add', payload: pname })}  fruits={fruits}></AddFruit>
            <FruitList setFruit={setFruit} fruits={fruits}></FruitList>
        </div>
    );
}

实现的功能完全一样。但是一个全局的状态就实现了共享。

useContext

上面有个问题,就是AddFruit组件与父组件存在耦合。这时应该考虑解耦的问题。

useContext用于在快速在函数组件中导入上下文。把provide作为所有元素的老爹。隔代传参。

代码语言:javascript
复制
import React, { useContext } from "react"; // 创建上下文
const Context = React.createContext();
export default function HooksTest() {
    // ...
    return (
        {/* 提供上下文的值 */ }
        < Context.Provider value = {{ fruits, dispatch }}>
          <div>
            {/* 这里不再需要给FruitAdd传递变更函数,实现了解耦 */}
              <AddFruit />
          </div>
            </Context.Provider >
); }
function AddFruit(props) {
    // 获取上下文
    const { dispatch } = useContext(Context) 
    const onAddFruit = e => {
        if (e.key === "Enter") {
            // 直接派发动作修改状态
            dispatch({ type: "add", payload: pname }) setPname("");
        }
    };
    // ...
}

清爽多了!

不过对于傻瓜组件,可以不考虑接耦。也不见得这种方法完全取代redux。

React表单组件设计

除了重构,还有一个重要的地方是造轮子。

antd的表单实现
  1. import React from 'react'
  2. import antd from 'antd'
  3. const { Form, Icon, Input, Button} = antd;
  4. class NormalLoginForm extends React.Component {
  5. handleSubmit = e => {
  6. e.preventDefault();
  7. this.props.form.validateFields((err, values) => {
  8. if (!err) {
  9. console.log('Received values of form: ', values);
  10. }
  11. });
  12. };
  13. render() {
  14. const { getFieldDecorator } = this.props.form;
  15. return (
  16. <Form onSubmit={this.handleSubmit} className="login-form">
  17. <Form.Item>
  18. {getFieldDecorator('username', {
  19. rules: [{ required: true, message: 'Please input your username!' }],
  20. })(
  21. <Input
  22. prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
  23. placeholder="Username"
  24. />,
  25. )}
  26. </Form.Item>
  27. <Form.Item>
  28. {getFieldDecorator('password', {
  29. rules: [{ required: true, message: 'Please input your Password!' }],
  30. })(
  31. <Input
  32. prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
  33. type="password"
  34. placeholder="Password"
  35. />,
  36. )}
  37. </Form.Item>
  38. <Form.Item>
  39. <Button type="primary" htmlType="submit" className="login-form-button">
  40. Log in
  41. </Button>
  42. </Form.Item>
  43. </Form>
  44. );
  45. }
  46. }
  47. const WrappedNormalLoginForm = Form.create({ name: 'normal_login' })(NormalLoginForm);
  48. export default WrappedNormalLoginForm;

这是一个带有完整校验功能的表单。开发表单组件,至少考虑三个问题:

  • 数据收集
  • 校验
  • 提交

表单的结构如下

代码语言:javascript
复制
| - Form
  |-FormItem
   |-校验规则渲染下的表单组件

校验是怎么实现的?留意 getFieldDecorator:作用是封装表单组件为更强功能(可校验)的组件。

代码语言:javascript
复制
const { getFieldDecorator } = this.props.form;

代码里没有提及 this.props.form是如何创建的,这其实是一个高阶组件,参数包括字段名/校验规则/返回的表单元素。

倒数第二行:

代码语言:javascript
复制
const WrappedNormalLoginForm = Form.create({ name: 'normal_login' })(NormalLoginForm);

通过一个create工厂函数变成一个高阶组件,返回功能正式的组件。

设计思想:假设有一个组件,只管样式。通过高阶组件的处理,就成了一个完整功能的表单。

如何收集数据?那得看提交方法:

代码语言:javascript
复制
handleSubmit = e => {
    e.preventDefault();
    this.props.form.validateFields((err, values) => {
      if (!err) {
        console.log('Received values of form: ', values);
      }
    });
  };

又是 this.props.form提供了一个 validateFields方法。包括校验结果 errvalues值。

造轮子第一步

做一个类似antd的表单组件,不妨叫他为 dantd.

需求:先实现一个登录表单吧!

代码语言:javascript
复制
import React, { Component } from "react";



export default class DFormTest extends Component {
    render() {
        return (
            <div>
                <input type="text" /> 
                <input type="password" /> 
                <button>登录</Button>
            </div>
        );
    }
}

那么就有一个样子了。

getFieldDec怎样才能加载上到form上?

于是问题就是做高阶组件,可以扩展现有表单,包括以上三个功能:

  • 控件包装
  • 时间处理
  • 事件处理

于是我们可以着手来写这个高阶组件函数

代码语言:javascript
复制
function dFormCreate(Component){
    return class extends React.Component{
        constructor(props){
            super(props)
            // 期望用户能给传配置选项
            this.option={};
            this.state={};
        }
        /**
         * 包装函数
         * 接收字段名/校验配置
         * 返回一个高阶组件
         */
        getFieldDec=(field,option)=>{
            this.option[field]=option;//选项告诉我们如何校验
            return InputComp =>(
                <div>
                    {/* vdom不能修改,克隆一份再扩展 */}
                    {React.cloneElement(InputComp,{
                        name:field,
                        value:this.state[field]||'',
                        onChange:this.handleChange //执行校验,设置状态
                    })}
                </div>
            )
        }

        render(){

            return (
                <Component {...this.props} getFieldDec={this.getFieldDec}/>
            )
        }
    }
}
@dFormCreate
class DFormTest extends Component {...}

此时DFormTest已经有了 getFieldDec。这个高阶修饰器就可以进一步处理表单元素:

让表单元素获得各种属性
代码语言:javascript
复制
@dFormCreate
class DFormTest extends Component {

    render() {
        const { getFieldDec } = this.props;
        return (
            <div style={{ width: '60%', margin: 'auto' }}>
                {getFieldDec('username', {
                    rules: [{ required: true, message: 'Please input your username!' }],
                })(<input type="text" />)}

                {getFieldDec('password', {
                    rules: [{ required: true, message: 'Please input your password' }],
                })(<input type="password" />)}               
                <button>login</button>
            </div>
        );
    }
}
收集表单数据
代码语言:javascript
复制
handleChange=(e)=>{
            const {name,value}=e.target;
            this.setState({
                [name]:value
            })
        }
添加校验(validateField)
代码语言:javascript
复制
function dFormCreate(Component) {
    return class extends React.Component {
        constructor(props) {
            super(props)
            // 期望用户能给传配置选项
            this.options = {};
            this.state = {};
        }

        handleChange = (e) => {
            const { name, value } = e.target;

            this.setState({
                [name]: value
            }, () => {
                //单字段校验
                this.validateField(name);
            })
        }

        validateField = (field) => {
            //在此校验
            const rules = this.options[field].rules;

            // some里只要任何一项不通过,就不通过并跳出。
            const isValid = !rules.some(rule => {
                if (rule.required) {
                    if (!this.state[field]) {
                        // 校验失败
                        this.setState({
                            [field + 'Message']: rule.message
                        })
                        return true;
                    }
                }
                return false;
            });

            if (!isValid) {
                this.setState(
                    { [field + 'Message']: '' }
                )
            }

            return isValid;

        }

        //多个校验
        validateFields = (cb) => {
            // 将选项中所有field组成的数组转换为它们校验结果数组
            const rets = Object.keys(this.options).map((field) => {
                return this.validateField(field)
            })
            // 校验结果中每一项都要求true
            const ret = rets.every(v => v === true);
            console.log(222,this.state)
            cb(ret, this.state);
        }


        /**
         * 包装函数
         * 接收字段名/校验配置
         * 返回一个高阶组件
         */
        getFieldDec = (field, option) => {

            this.options[field] = option;//选项,告诉我们如何校验
            return InputComp => (
                <div>
                    {/* vdom不能修改,克隆一份再扩展 */}
                    {React.cloneElement(InputComp, {
                        name: field,
                        value: this.state[field] || '',
                        onChange: this.handleChange //执行校验,设置状态
                    })}
                </div>
            )
        }

        render() {

            return (
                <Component {...this.props} validateFields={this.validateFields} getFieldDec={this.getFieldDec} validate={this.validate} />
            )
        }
    }
}
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-06-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一Li小麦 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Hook
    • 状态钩子 State Hook
      • 副作用钩子 Effect Hook (类似watch)
        • useReducer (状态管理lowb实现)
          • useContext
          • React表单组件设计
            • antd的表单实现
              • 造轮子第一步
                • getFieldDec怎样才能加载上到form上?
                  • 让表单元素获得各种属性
                    • 收集表单数据
                      • 添加校验(validateField)
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档