一、安装和创建项目
// 全局安装
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
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,有利于只更新更改的数据,最好不要设置为下标,防止其中一数据更改时,所有数据在外层包裹标签中移除重新渲染
性能优化,引用类型
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:调用接口、接收路由参数
网友评论