react

2020-08-26  本文已影响0人  致自己_cb38

一、安装和创建项目

// 全局安装
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:调用接口、接收路由参数
上一篇下一篇

猜你喜欢

热点阅读