胖蔡叨叨叨
你听我说

React babel配置

xuao阅读(647)

用react开发企业微信的web项目,会发现在pc端的企业微信内置浏览器显示空白,即react项目没有运行,只执行到第一个index.html文件。初步发现发生这种情况的原因是pc端企业微信内置浏览器不支持es6语法,而react使用的是es6语法,知道原因后再去修改就很容易了。那是不是只要把es6语法转成其支持的es5语法即可以了呢?答案是是的。

babel安装

详细内容查看babel中文网 安装依赖:

  • yarn add @babel/cli
  • yarn add @babel/parser
  • yarn add @babel/plugin-proposal-class-properties
  • yarn add @babel/plugin-transform-react-jsx
  • yarn add @babel/polyfill
  • yarn add @babel/preset-flow
  • yarn add @babel/preset-react
  • yarn add @babel/core
  • yarn add @babel/preset-env
  • yarn add babel-loader

由于项目比较赶,至于为什么要安装这么多的依赖我自己也还没有弄清楚,最终依赖了这么多完全是根据安装时控制台的报错信息一步一步安装出来的。

babel配置

工程根目录下新建文件 babel.config.json,并添加内容:

{
    "presets":[
        "@babel/preset-react",
        "@babel/preset-env"
    ],
    "plugins": ["@babel/plugin-proposal-class-properties"]
}

配置比网上找的其他文章都要简单,我不知道是不是我使用了蚂蚁样式库的原因,由于主要在移动端使用,所以用了 ant design mobile 的库,package.json的配置如下,可供参考:

{
...
 "dependencies": {
    "@babel/cli": "^7.10.5",
    "@babel/parser": "^7.11.0",
    "@babel/plugin-proposal-class-properties": "^7.10.4",
    "@babel/plugin-transform-react-jsx": "^7.10.4",
    "@babel/polyfill": "^7.10.4",
    "@babel/preset-flow": "^7.10.4",
    "@babel/preset-react": "^7.10.4",
    "antd-mobile": "^2.3.3",
    "babel-plugin-import": "^1.13.0",
    "react": "^16.13.1",
    "react-app-rewired": "^2.1.6",
    "react-dom": "^16.13.1",
    "react-scripts": "3.4.1",
    ...
  },
 "scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test --env=jsdom",
    "eject": "react-scripts eject"
  },
 "devDependencies": {
    "@babel/core": "^7.11.0",
    "@babel/preset-env": "^7.11.0",
    "babel-loader": "^8.1.0"
  }
...
}

使用

此时执行 npm run build命令,将打包后的全部文件放到服务器访问目录就可以在pc端企业微信内置浏览器访问项目,且正常运行。

计划

每次修改都需要打包后检查修改的结果,我喜欢按帧编程,那么这样就特别麻烦,所以我想有没有实时转码的方式呢?

useState 函数式状态管理

胖蔡阅读(10695)

React 有两种方式可以实现组件的定义,一种是通过函数实现,一种是通过class类实现。一般的通过class类实现的组件我们可以看到足够多的生命周期、状态state可供操作,而函数式组件则没有生命周期和状态值state。对就性能而言,由于函数组件需要初始化组件,而函数组件是直接通过return 返回JSX,因此函数组件的性能要优于类组件。为了性能考虑,一般的我们推荐使用函数组件来实现组件的定义。

出于操作考虑,React自从16.8.0版本开始支持Hook功能,让我们可以通过hook方式在函数组件中实现state、生命周期的监听。这里,我们介绍hook的一种用法useState,通过useState来实现状态值的操作。操作之前,需要注意几点:

  • 只能在React函数中调用 即是必须在组件函数中调用,不可在一般的js中使用useState等hook实现。
  • 在函数返回部分顶部调用 不可在jsx代码部分使用hook实现,须得如同js的变量定义方式使用useState等hook实现。

Class状态管理

在没有hook之前,我们通常使用class组件的方式来实现状态的管理:

import React, { Component } from 'react';

export default class extends Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date(),counter:0};
  }

  

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
         <button onClick={() => this.state((state, props) => ({
              counter: state.counter + 1}))}>
        Click me
      </button>
      </div>
    );
  }

} 

当使用useState时

当使用hook在函数组件中实现状态管理,对我们而言操作是变得更简单了。

import React, { useState } from 'react';

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
    </>
  );
}

如何使用useState

通过上面的示例,我们基本可以简单的总结useState的使用方法。

  • 定义state 我们可以通过useState来定义一个state对象:
//useState格式
const [state, setState] = useState(initialState);

useState通过传入一个初始的对象值initialState,从而返回一个状态值state(初始状态值为initialState),和一个更新状态的更新函数setState

  • 使用更新函数 使用上述返回的更新函数来更新状态值:
setState(state+1);

如何在React工程中配置less支持?

胖蔡阅读(516)

Less是css的一个变种,css的一个高级体现,与其类似的还有sass,都是一种编译型样式语言。这样的语言对于程序员而言,具有更高的可读性,理解起来也容易很多,层次性较好,这在这几年市场的选择也可以明显看出。如此,这就需要我们对当前的项目进行less依赖,从而让我们的项目支持less。

依赖下载

$yarn add less less-loader #或者
$npm install less less-loader

无论是less亦或者是sass都是通过对文件的编译生成新的css的过程。因此,如果我们想要库能实现less支持,需要先加载基础的依赖库。

修改配置文件

依赖安装好了后,我们还需要修改我们的less配置,以实现webpack在打包时同时编译打包less文件,想要修改配置需得先将配置文件找到,webpack脚手架为安全考虑,默认是将config文件隐藏。因此,我们需要先暴露配置文件,然后再修改配置。

  • 获取配置文件

获取配置文件可通过如下脚本触发。

$yarn eject

  • 修改config/webpack.config.js

我们需要在webpack.config.js中添加css的模块规则,配置类似其中的css配置方式:

const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;
//我们添加的less配置
const lessRegex = /\.less$/;
const lessModuleRegex = /\.module\.less$/;
......
{
                            test: lessRegex,
                            exclude: lessModuleRegex,
                            use: getStyleLoaders({
                                    importLoaders: 3,
                                    sourceMap: isEnvProduction && shouldUseSourceMap,
                                },
                                'less-loader'
                            ),
                            // Don't consider CSS imports dead code even if the
                            // containing package claims to have no side effects.
                            // Remove this when webpack adds a warning or an error for this.
                            // See https://github.com/webpack/webpack/issues/6571
                            sideEffects: true,
                        }, {
                            test: lessModuleRegex,
                            use: getStyleLoaders({
                                    importLoaders: 3,
                                    sourceMap: isEnvProduction && shouldUseSourceMap,
                                    modules: {
                                        getLocalIdent: getCSSModuleLocalIdent,
                                    },
                                },
                                'less-loader'
                            ),
                        },

使用

如此就完成了less在react中的配置,实现了react中支持less文件。之后我们就可以创建less并在js中导入less文件来生效样式。

/**index.less **/

body{
  font-size:14px;
  height:100%;
  .main {
     max-width:1200px;
     height:100%;
  }
}

React 上下文Context的使用

胖蔡阅读(10324)

依照3W学习原则的模式出发来思考Context的使用问题,在学会如何使用Context之前,首先我们需要来思考一下几个问题:

  •  Context是什么?
  •  为什么需要用Context?
  •  怎么样使用Context?

使用之前较为清晰的了解使用研究对象更加有助于我们的认知和加深我们对Context的理解程度。

Context是什么?

Context(上下文)一直在各种语言、平台之中占据较为重要的地位,它是整个程序的链接者,一直贯穿应用的整个生命周期。React中的Context也类似,Context旨在为React复杂嵌套的各个组件提供一个生命周期内的统一属性访问对象,从而避免我们出现当出现复杂嵌套结构的组件需要一层层通过属性传递值得问题。

其设计之初的构想就是为了提供一个组件树形结构内的一个数据共享的容器。

为什么需要用Context?

为何要用Context,前文也有所介绍,其主要原因还是由于不使用Context无法完成某些业务场景或者是实现起来很难。我们可以通过两张图来了解下在是否使用Context场景下,其对应的属性值传递的过程。来帮助我们了解为什么Context非用不可。

  • 不使用Context
    38c97f991cc3b41

传统方式不使用Context,数据的传递如上图属于嵌套关系,只能通过有父子包含关系的组件一层层进行传递,数据传递的层级随着结构的增加而成倍增加。

  • 使用Context
49ee2ce83f6e77a

使用Context方式进行数据的共享,各个树状组件均可通过统一的Consumer统一访问全局的Consumer共享数据。

怎么样使用Context?

Context的使用主要分为创建、插入、访问三个流程,通过创建context对象提供共享组件,将Provider指定嵌套至需要使用共享数据的顶层结构,然后各后代组件通过context访问共享数据(变量、常量、方法等)。接下来,通过一个封装的登录组件来学习使用Context。

创建一个context

首先,我们需要创建一个待使用的context对象放在一边,等待使用的使用导入,我们可以将其封装成一个单独的js文件并将其export:

// 创建文件LoginContext.js
import { createContext } from 'react';

const LoginContext = createContext({});
export default LoginContext;

Provider包装一个组件

为了降低程序各个功能块的耦合性,防止Context共享数据的滥用和误用。一般的,我们可以将单独的一个模块(需要使用共享数据),进行一层封装,封装成一个单独的组件使用,如全局的多语言切换就可以在顶级组件App内封装。

//创建一个LoginFrom.js
import { Tabs, Form } from 'antd';
import React, { useState } from 'react';
import useMergeValue from 'use-merge-value';
import classNames from 'classnames';
import LoginContext from './LoginContext';
import LoginItem from './LoginItem';
import LoginSubmit from './LoginSubmit';
import LoginTab from './LoginTab';
import styles from './index.less';

const Login = props => {
  const { className } = props;
  const [tabs, setTabs] = useState([]);
  const [active, setActive] = useState();
  const [type, setType] = useMergeValue('', {
    value: props.activeKey,
    onChange: props.onTabChange,
  });
  const TabChildren = [];
  const otherChildren = [];
  React.Children.forEach(props.children, child => {
    if (!child) {
      return;
    }

    if (child.type.typeName === 'LoginTab') {
      TabChildren.push(child);
    } else {
      otherChildren.push(child);
    }
  });
  return (
    <LoginContext.Provider
      value={{
        tabUtil: {
          addTab: id => {
            setTabs([...tabs, id]);
          },
          removeTab: id => {
            setTabs(tabs.filter(currentId => currentId !== id));
          },
        },
        updateActive: activeItem => {
          if (active[type]) {
            active[type].push(activeItem);
          } else {
            active[type] = [activeItem];
          }

          setActive(active);
        },
      }}
    >
      <div className={classNames(className, styles.login)}>
        <Form
          form={props.from}
          onFinish={values => {
            if (props.onSubmit) {
              props.onSubmit(values);
            }
          }}
        >
          {tabs.length ? (
            <React.Fragment>
              <Tabs
                animated={false}
                className={styles.tabs}
                activeKey={type}
                onChange={activeKey => {
                  setType(activeKey);
                }}
              >
                {TabChildren}
              </Tabs>
              {otherChildren}
            </React.Fragment>
          ) : (
            props.children
          )}
        </Form>
      </div>
    </LoginContext.Provider>
  );
};

Login.Tab = LoginTab;
Login.Submit = LoginSubmit;
Login.UserName = LoginItem.UserName;
Login.Password = LoginItem.Password;
Login.Mobile = LoginItem.Mobile;
Login.Captcha = LoginItem.Captcha;
export default Login;

从上面的代码我们可以看到LoginContext.Provider 提供了一个共享对象:

 <LoginContext.Provider
      value={{name:'title',value:'Provider'}}
    >
    ......

    </LoginContext.Provider>
      

一般的我们通过设置value值来指定共享数据的内容。

调用共享数据

通过上面的创建和定义,接下来我们就可以在Provider包含组件内进行共享数据的调用获取,而不需要一层层的去传递了。

//创建文件LoginTab.js
import React, { useEffect } from 'react';
import { Tabs } from 'antd';
import LoginContext from './LoginContext'; // 创建的共享context

const { TabPane } = Tabs;

const generateId = (() => {
  let i = 0;
  return (prefix = '') => {
    i += 1;
    return `${prefix}${i}`;
  };
})();

const LoginTab = props => {
  useEffect(() => {
    const uniqueId = generateId('login-tab-');
    const { tabUtil } = props;

    if (tabUtil) {
      tabUtil.addTab(uniqueId);
    }
  }, []);
  const { children } = props;
  return <TabPane {...props}>{props.active && children}</TabPane>;
};

const WrapContext = props => (
  <LoginContext.Consumer>
    {value => <LoginTab tabUtil={value.tabUtil} {...props} />}
  </LoginContext.Consumer>
); // 标志位 用来判断是不是自定义组件

WrapContext.typeName = 'LoginTab';
export default WrapContext;

上面的代码可以看出,我们可以通过LoginContext.Consumer包裹器包裹获取共享数据Context,其获取数据格式:

 <LoginContext.Consumer>
    {value => (/**这里可以渲染对应的jsx代码,value就是Provider中提供的value**/)}
  </LoginContext.Consumer>
'); })();