react

作者: 致自己_cb38 | 来源:发表于2020-08-26 16:48 被阅读0次

    一、安装和创建项目

    // 全局安装
    npm install -g create-react-app
    
    // 创建项目
    create-react-app projectName
    
    // 运行项目
    npm start
    

    二、教程

    官网
    简易教程
    移动端框架
    安装

    npm i antd-mobile或者yarn add antd-mobile 
    

    babel-plugin-import按需引入
    babel-plugin-import按需引入GitHub

    index.js:项目入口文件,文件名不可更改

    // 引入核心模块
    import React from "react";
    import ReactDOM from "react-dom";
    import "./index.css";
    // 引入组件
    import App from "./App";
    import * as serviceWorker from "./serviceWorker";
    
    // 把对应的内容渲染到id为root的标签上,参数1:root内部内容,参数2:渲染到/public/index.html中的哪个标签
    ReactDOM.render(
        <React.StrictMode>
            <App />
        </React.StrictMode>,
        document.getElementById("root")
    );
    
    // If you want your app to work offline and load faster, you can change
    // unregister() to register() below. Note this comes with some pitfalls.
    // Learn more about service workers: https://bit.ly/CRA-PWA
    serviceWorker.unregister();
    

    html代码自动补全

    image.png
    "emmet.includeLanguages": {
        "javascript": "javascriptreact"
    },
    "emmet.triggerExpansionOnTab": true
    

    快捷代码提示,安装以下插件

    image.png

    如:创建组件快捷代码rcc


    image.png

    定义组件

    // 法一:
    import React from "react";
    import logo from "./logo.svg";
    import "./App.css";
    
    // 定义一个组件
    function App() {
        // 渲染
        return (
            <div className="App">
                <header className="App-header">
                    <img src={logo} className="App-logo" alt="logo" />
                    <p>
                        Edit <code> src / App.js </code> and save to reload.{" "}
                    </p>{" "}
                    <a
                        className="App-link"
                        href="https://reactjs.org"
                        target="_blank"
                        rel="noopener noreferrer"
                    >
                        Learn React{" "}
                    </a>{" "}
                </header>{" "}
            </div>
        );
    }
    export default App;
    
    // 法二:
    class Welcome extends React.Component {
      render() {
        return <h1>Hello, {this.props.name}</h1>;
      }
    }
    

    路由

    路由简易教程
    阮一峰路由教程

    // 安装
    npm i react-router@3.2.0
    yarn add react-router@3.2.0
    
    // index.js
    // 引入核心模块
    import React from "react";
    import ReactDOM from "react-dom";
    import "./index.css";
    import App from "./App";
    import * as serviceWorker from "./serviceWorker";
    import routes from "./router";
    
    // 把对应的内容渲染到id为root的标签上,参数1:root内部内容,参数2:渲染到/public/index.html中的哪个标签
    ReactDOM.render(routes, document.getElementById("root"));
    
    // If you want your app to work offline and load faster, you can change
    // unregister() to register() below. Note this comes with some pitfalls.
    // Learn more about service workers: https://bit.ly/CRA-PWA
    serviceWorker.unregister();
    
    
    // App.js
    import React, { Component } from "react";
    import "./index.css";
    import { Link } from "react-router";
    
    // 父组件
    class App extends Component {
        render() {
            return (
                <ul>
                    <li>
                        {/* 跳转路由 方法:this.props.router.push("/index")*/}
                        <Link to="/index">首页</Link>
                    </li>
                    <li>
                        <Link to="/list">列表页</Link>
                    </li>
                </ul>
            );
        }
    }
    
    export default App;
    
    
    // router.js
    import React from "react";
    import Index from "./page/index";
    import List from "./page/list";
    import Other from "./page/other";
    import App from "./App";
    
    // 引入路由
    import {
        Route,
        Router,
        hashHistory,
        Redirect,
        IndexRedirect,
    } from "react-router";
    
    let routes = (
        <Router history={hashHistory}>
            <Route path="/" component={App} />
            {/* 路由传参 */}
            <Route path="/index/:id" component={Index} />
            <Route path="/list" component={List}>
                {/* 重定向,必须在外面包裹Route */}
                {/* <Redirect from="*" to="/" /> */}
                {/* 访问根路由的时候,将用户重定向到某个子组件,必须在外面包裹Route */}
                {/* <IndexRedirect to="/index" /> */}
                {/* 嵌套路由 */}
                <Route path="other" component={Other} />
            </Route>
        </Router>
    );
    
    export default routes;
    
    
    // page/list.js
    import React, { Component } from "react";
    
    export class list extends Component {
        render() {
            return (
                <div>
                    表单页面
                    <a href="#/list/other">子页面</a>
                    {/* 渲染子路由页面 */}
                    {this.props.children}
                </div>
            );
        }
    }
    
    export default list;
    
    
    // page/index.js
    import React, { Component } from "react";
    
    export class index extends Component {
        render() {
            // 获取路由参数
            return <div> 首页 {this.props.routeParams.id} </div>;
        }
    }
    
    export default index;
    

    测试技巧,mock,模拟数据

    React使用概览,强制更新

    ReactDOM,渲染

    DOM 元素,例如checked、style、dangerouslySetInnerHTML等

    合成事件,所有可调用的方法

    Hook,不编写 class 的情况下使用 state 以及其他的 React 特性,useState

    Hook API 索引,使用概览

    组件 & props,所有 React 组件都必须像纯函数一样保护它们的 props 不被更改

    State & 生命周期

    双向数据绑定

    import React, { Component } from "react";
    
    export class App extends Component {
        constructor(props) {
            super(props);
            this.state = {
                val: "haha",
            };
        }
    
        handleChange(e) {
            this.setState({
                val: e.target.value,
            });
        }
    
        render() {
            return (
                <div>
                    <p>{this.state.val}</p>
                    <input
                        type="text"
                        value={this.state.val}
                        onChange={(e) => this.handleChange(e)}
                    />
                </div>
            );
        }
    }
    
    export default App;
    

    协调,key,组件生命周期:类似vue的mounted、updated、destroyed

    https://www.jianshu.com/p/b331d0e4b398

    image.png

    挂载

    constructor
    UNSAFE_componentWillMount
    render
    componentDidMount
    

    更新

    // 接收即将更新的props之前
    UNSAFE_componentWillReceiveProps
    // props和state更新
    shouldComponentUpdate
    // 上一步为true执行以下代码,false反之,可在渲染之前加载loading
    UNSAFE_componentWillUpdate
    render
    componentDidUpdate
    
    image.png image.png

    卸载

    // 执行清理工作,如删除监听事件、清除定时器等
    componentWillUnmount
    
    image.png image.png

    销毁DOM后,事件依然存在


    image.png

    清除事件


    image.png

    事件处理

    条件渲染

    列表 & Key

    表单

    组合 vs 继承,类似vue的slot

    父传子、props默认值、插槽

    import React, { Component } from "react";
    import "./index.css";
    
    // 子组件
    class Header extends Component {
        // props的默认值
        static defaultProps = {
            bgc: "skyblue",
            title: "默认标题",
        };
    
        render() {
            return (
                // this.props:父组件的标签属性
                <header style={{ backgroundColor: this.props.bgc }}>
                    {this.props.title}
                    {/* 相当于vue的插槽 */}
                    {this.props.children}
                </header>
            );
        }
    }
    
    // 父组件
    class App extends Component {
        constructor(props) {
            super(props);
            this.state = {
                title: "首页",
            };
        }
    
        render() {
            return (
                <div>
                    <Header title={this.state.title} bgc="pink">
                        插槽
                    </Header>
                    <Header />
                </div>
            );
        }
    }
    
    export default App;
    
    

    子传父

    import React, { Component } from "react";
    import "./index.css";
    import PropTypes from "prop-types";
    
    // 子组件
    class Child extends Component {
        // 调用父组件的方法
        handleChangeNum() {
            this.props.onFatherChange(30);
        }
    
        render() {
            return <button onClick={this.handleChangeNum.bind(this)}>点击</button>;
        }
    }
    
    // 父组件
    class App extends Component {
        constructor(props) {
            super(props);
            this.state = {
                num: 0,
            };
        }
    
        // 修改值
        handleChange(val) {
            this.setState({ num: val });
        }
    
        render() {
            return (
                <div>
                    <p>{this.state.num}</p>
                    {/* 传递父组件的方法给子组件 */}
                    <Child onFatherChange={this.handleChange.bind(this)} />
                </div>
            );
        }
    }
    
    export default App;
    

    Context:很多不同层级的组件需要访问同样一些的数据,包括管理当前的 locale,theme,或者一些缓存数据

    props多级传递

    import React, { Component } from "react";
    import "./index.css";
    import PropTypes from "prop-types";
    
    // 创建共享数据
    const TitleContext = React.createContext({
        title: "共享数据",
    });
    
    // 孙子组件
    class Childchild extends Component {
        // 获取共享属性和类型
        static contextType = TitleContext;
    
        render() {
            // 获取方式
            return <div>{this.context.title}</div>;
        }
    }
    
    // 子组件
    class Child extends Component {
        render() {
            return <Childchild />;
        }
    }
    
    // 父组件
    class App extends Component {
        render() {
            return (
                <div>
                    <Child />
                </div>
            );
        }
    }
    
    export default App;
    

    状态提升,共享最近父级的变量,在子级中调用父级的方法,修改父级变量值

    Render Props,共享代码

    Redux 中文文档,相当于vuex,用于复杂传值

    安装

    yarn add redux react-redux或者npm i redux react-redux
    

    简书
    简易教程
    阮一峰教程
    react

    image.png image.png image.png

    react-redux


    image.png image.png image.png image.png
    // app.js
    import React, { Component } from "react";
    import { Route, Router, hashHistory } from "react-router";
    import { createStore } from "redux";
    import { Provider } from "react-redux";
    import ReactDOM from "react-dom";
    import LoginPage from "./LoginPage";
    import HomePage from "./HomePage";
    import ListPage from "./ListPage";
    import DetailPage from "./DetailPage";
    
    const defaultState = {
        nav_list_data: [],
    };
    
    // 管理仓库
    const reducer = (state = defaultState, action) => {
        if (action.type === "init_subject_data") {
            // 深拷贝
            let newState = JSON.parse(JSON.stringify(state));
            newState.nav_list_data = action.value;
            return newState;
        }
        return state;
    };
    
    // 创建仓库
    const store = createStore(reducer);
    
    // export class App extends Component {
    //  render() {
    //      return (
    //          <LoginPage />
    //          <HomePage />
    //          <ListPage />
    //          <DetailPage />
    //      );
    //  }
    // }
    
    const appRouter = (
        <Provider store={store}>
            <Router history={hashHistory}>
                <Route path="/" component={LoginPage}></Route>
                <Route path="/home" component={HomePage}></Route>
                <Route path="/list" component={ListPage}></Route>
                <Route path="/detail" component={DetailPage}></Route>
            </Router>
        </Provider>
    );
    
    ReactDOM.render(appRouter, document.getElementById("root"));
    
    
    // subject.js
    import React, { Component } from "react";
    import { Flex } from "antd-mobile";
    import "../assets/styles/subject.less";
    import { connect } from "react-redux";
    import Axios from "axios";
    
    class Subject extends Component {
        // 获取后端数据
        componentDidMount() {
            Axios.get("/api/subject.json").then((res) => {
                // console.log(res.data.data);
                // 调用仓库方法修改数据
                this.props.init_subject_data(res.data.data);
            });
        }
    
        render() {
            return (
                <div className="subject">
                    {/* 获取仓库数据 */}
                    {this.props.nav_list_data.map((item, index) => {
                        return (
                            <Flex key={index}>
                                {item.map((flexItem, flexIndex) => {
                                    return (
                                        <Flex.Item key={flexIndex}>
                                            <a href="#/list">
                                                <i
                                                    style={{
                                                        backgroundImage: "url(" + flexItem.url + ")",
                                                    }}
                                                ></i>
                                                <p> {flexItem.name} </p>
                                            </a>
                                        </Flex.Item>
                                    );
                                })}
                            </Flex>
                        );
                    })}
                </div>
            );
        }
    }
    
    // 获取仓库数据
    const mapStateToProps = (state) => {
        return {
            nav_list_data: state.nav_list_data,
        };
    };
    
    // 修改仓库数据
    const mapDispatchProps = (dispatch) => {
        return {
            init_subject_data(val) {
                let action = {
                    type: "init_subject_data",
                    value: val,
                };
                dispatch(action);
            },
        };
    };
    
    export default connect(mapStateToProps, mapDispatchProps)(Subject);
    

    代码分割,打包,懒加载,重命名组件

    错误边界,捕获并打印发生在其子组件树任何位置的 JavaScript 错误,并且,它会渲染出备用 UI,类似于 JavaScript 的 catch {}

    Fragments,相当于uni-app的block,作为包裹标签,但不渲染到DOM中

    高阶组件,参数为组件,返回值为新组件的函数

    与第三方库协同,专注于与 jQuery 和 Backbone 进行整合

    深入 JSX,遍历,类似vue的slot,v-if等

    const list= ['finish doc', 'submit pr', 'nag dan to review'];
    return(
      <ul>
        {
          list.map((item) => <Item key={item} />)
        }  
      </ul>
    )
    
    return(
      <ul>
        {
          list.map((item,index) => {
            return(
              <li  key={key}>{item}</li>
            )
          })
        }  
      </ul>
    )
    

    给遍历中的根标签添加key防止报错,有利于提高更新效率,减少不必要的DOM操作,一般设置为数据的id,有利于只更新更改的数据,最好不要设置为下标,防止其中一数据更改时,所有数据在外层包裹标签中移除重新渲染

    image.png

    性能优化,引用类型

    var player = {score: 1, name: 'Jeff'};
    
    // player 的值没有改变, 但是 newPlayer 的值是 {score: 2, name: 'Jeff'}
    var newPlayer = Object.assign({}, player, {score: 2});
    
    // 使用对象展开语法,就可以写成:
    var newPlayer = {...player, score: 2};
    

    Portals将子节点渲染到存在于父组件以外的 DOM 节点,对话框、悬浮卡以及提示框等

    Profiler,识别出应用中渲染较慢的部分

    不使用 ES6,this

    必须给事件绑定this,防止报错


    image.png
    // 法一:
    <button onClick={this.handleClick.bind(this)}>点击</button>
    
    // 法二:
    <button onClick={() => this.handleClick()}>点击</button>
    
    // 法三:目前还处于试验性阶段,不建议使用
    handleClick = () => {
            this.setState({
                num: this.state.num + 1,
            });
    };
    
    // 法四:
    constructor(props) {
            super(props);
            this.state = {
                num: 0,
            };
            this.handleClick = this.handleClick.bind(this);
    }
    
    // 法五:
    var SayHello = createReactClass({
      getInitialState: function() {
        return {message: 'Hello!'};
      },
    
      handleClick: function() {
        alert(this.state.message);
      },
    
      render: function() {
        return (
          <button onClick={this.handleClick}>
            Say hello
          </button>
        );
      }
    });
    

    不使用 JSX

    静态类型检查,检查代码bug

    严格模式,突出显示应用程序中潜在问题

    使用 PropTypes,props 上进行类型检查

    子组件中验证props属性值的类型
    传入值与子组件中指定props属性值类型不符,报以下错


    image.png
    // 最新的react中已经默认安装以下库,可直接引入
    // npm i prop-types
    // 或者
    // npm i yarn -g
    // yarn add prop-types
    
    import React, { Component } from "react";
    import "./index.css";
    import PropTypes from "prop-types";
    
    // 子组件
    class Header extends Component {
        // 验证props属性值的类型
        static propTypes = {
            title: PropTypes.number,
        };
    
        render() {
            return (
                // this.props:父组件的标签属性
                <header style={{ backgroundColor: this.props.bgc }}>
                    {this.props.title}
                </header>
            );
        }
    }
    
    // 父组件
    class App extends Component {
        constructor(props) {
            super(props);
            this.state = {
                title: 123,
            };
        }
    
        render() {
            return (
                <div>
                    <Header title={this.state.title} bgc="pink"></Header>
                </div>
            );
        }
    }
    
    export default App;
    

    无障碍,ref

    Refs 转发

    Refs & DOM

    非受控组件,表单数据将交由 DOM 节点来处理,值只能由用户设置,而不能通过代码控制

    不受控组件:必须等到用户操作之后,才能获取到值

    class App extends Component {
        constructor(props) {
            super(props);
            this.myRef = React.createRef();
        }
    
        handleGetDom() {
            console.log(this.myRef.current.value);
        }
        render() {
            return (
                <div>
                    <input type="text" ref={this.myRef} />
                    <button onClick={this.handleGetDom.bind(this)}>点击</button>
                </div>
            );
        }
    }
    

    受控组件:授组件控制

    class App extends Component {
        constructor(props) {
            super(props);
            this.state = {
                num: 0,
            };
        }
    
        // 修改值
        handleChange(e) {
            this.setState({ num: e.target.value });
        }
    
        render() {
            return (
                <div>
                    <input
                        type="text"
                        value={this.state.num}
                        onChange={this.handleChange.bind(this)}
                    />
                    <button>点击</button>
                </div>
            );
        }
    }
    

    Web Components,为可复用组件提供了强大的封装,如:<x-search>{this.props.name}</x-search>

    ReactDOMServer允许你将组件渲染成静态标记。通常,它被使用在 Node 服务端上

    Test Utilities, 组件测试

    Test Renderer,将 React 组件渲染成纯 JavaScript 对象,无需依赖 DOM 或原生移动环境

    JavaScript 环境要求,支持老版本浏览器

    术语表

    Concurrent 模式,节流,防抖,并发等

    常问问题

    项目集成less

    解包脚手架

    yarn eject或者npm run eject
    

    npm start报错


    image.png

    解决:删除node_modules,执行以下命令

    npm i或者yarn add start
    

    安装less

    yarn add less less-loader -D
    

    在config/webpack.config.js中配置less加载器

    const lessModuleRegex = /\.less$/;
    
    {
        test: lessModuleRegex,
            use: getStyleLoaders(
                {
                    // 暂不配置
                },
            "less-loader"
        ),
    }
    

    初始化样式

    yarn add normalize.css或者npm i normalize.css
    

    axios

    yarn add axios或者npm i axios
    
    // FormData和Payload是浏览器传输数据给接口的其中两种格式,这两种方式浏览器是通过Content-Type来进行区分的
    // 如果是application/x-www-form-urlencoded,则为formdata方式,如果是application/json方式,则为payload方式
    // 需要转换成formdata方式,则要下载qs进行转换
    import qs from 'qs'
    axios.post('/user-server/login/userLogin',qs.stringify({
            userName:me.refs.name.value,
            password:me.refs.password.value
        })).then(res => {
            console.log(res);
    })
    

    项目部署

    image.png

    适配移动端

    https://www.jianshu.com/p/5b9878b70cfc
    https://zhuanlan.zhihu.com/p/148529375
    https://www.jianshu.com/p/8b85e49599af

    三、案例

    选项卡

    import React, { Component } from "react";
    import "./index.css";
    
    export class App extends Component {
        constructor(props) {
            super(props);
            // 声明变量
            this.state = {
                currentIndex: 0,
            };
        }
    
        // 当前点击标签,用于切换内容
        handleClick(i) {
            this.setState({
                currentIndex: i,
            });
        }
    
        // 封装遍历标签
        handleMap(options) {
            const tabs = [];
            // 必须首字母大写,否则视为一个不存在的名为tag的标签
            let Tag = options.tag;
            for (let i = 0; i < 3; i++) {
                const btn = (
                    <Tag
                        key={i}
                        className={
                            options.className && this.state.currentIndex == i
                                ? options.className
                                : ""
                        }
                        onClick={options.boolean ? this.handleClick.bind(this, i) : null}
                    >
                        {options.tag + i}
                    </Tag>
                );
                tabs.push(btn);
            }
            return tabs;
        }
        render() {
            return (
                <div>
                    <div>{this.handleMap({ tag: "button", boolean: true })}</div>
                    <div>{this.handleMap({ tag: "p", className: "conActive" })}</div>
                </div>
            );
        }
    }
    
    export default App;
    
    
    // index.css
    p {
      display: none;
    }
    
    .conActive {
      display: block;
    }
    

    四、其他

    class变className
    
    行间样式{{}}:style={{color:"#fff",fontSize:32}}
    
    行间变量{}:<div data={value}>{name}</div>
    
    label中的for变htmlFor
    
    
    // 类名三元运算符
    className={`btn ${this.props.isFull ? "isFull" : ""}`
    className={"btn " + (${this.props.isFull ? "isFull" : "")}
    
    
    // 简写
    <input
        type={this.props.type}
        placeholder={this.props.placeholder}
        value={this.props.value}
        onChange={this.props.onChange}
    />
    <input {...this.props} />
    
    
     // 路由link和a标签的区别
    <Link to="/home">
        <FormBtn isFull={true}> 登录 </FormBtn>
    </Link>
    
    <a href="#/home">
        <FormBtn isFull={true}> 登录 </FormBtn>
    </a>
    
    this.props.router.push("/home")
    
    
    防止点击2次跳转路由
    e.preventDefault();
    
    
    跨域 package.json
    "proxy":"http://localhost:3000"
    
    
    componentDidMount:调用接口、接收路由参数
    

    相关文章

      网友评论

          本文标题:react

          本文链接:https://www.haomeiwen.com/subject/yluwjktx.html