前言
总结了一下最近开发中用到的相关技能点,这里从应用层面入手,梳理一了遍各个知识点在实际开发中的使用方法,也总结了一些常用的解决方案。本意是给自己做一下阶段性总结,如果能帮到各位同学,那也是极好的。
class组件创建
这是一个基本class组件的创建。
import React, {Component} from 'react';
class App extends Component{
render(){
return <div>这是一个组件</div>;
};
}
export default App;
class组件的生命周期
-
constructor()
完成了React数据的初始化,它接受两个参数:props和context,当想在函数内部使用这两个参数时,需使用super()传入这两个参数。 注意:只要使用了constructor()就必须写super(),否则会导致this指向错误。
-
componentWillMount()
在数据初始化完毕后,DOM未渲染时调用。
-
componentDidMount()
在DOM节点生成后调用。
-
componentWillUnmount()
组件卸载数据销毁时调用,在这里进行监听、定时的移除。
-
componentWillReceiveProps(nextProps)
当前组件接收父组件props数据重新渲染时调用,在初始props不会被调用,nextProps是新传入的props参数
// 代替方案 static getDerivedStateFromProps(nextProps,prevState){ //该方法内禁止访问this if(nextProps.email !== prevState.email){ //通过对比nextProps和prevState,返回一个用于更新状态的对象 return { value:nextProps.email } } //不需要更新状态,返回null return null; }使用了
getDerivedStateFromProps后是无法访问this的,如果需要访问的话可以配合componentDidUpdate去使用componentDidUpdate(prevProps, nextProps){ // this.. } -
shoudlComponentUpdate
减少组件重复渲染,提高界面性能
shouldComponentUpdate(nextProps, nextState){ return false; }
渲染列表
import React, {Component} from 'react';
class List extends Component {
constructor(props){
super(props);
this.state = {
list: [
{ key: 0, name: '张三' },
{ key: 1, name: '李四' }
]
}
}
render(){
return (
<ul>
{
this.state.list.map(item => {
return <li key={item.key}>{item.name}</li>;
})
}
</ul>
);
}
}
修改state数据
注意:setState是个异步处理,要避免赋值后马上读取state数据,更不要直接this.state.xx = 'xx'
//this.setState({}, e=>{})
this.setState((state, props)=>{
return {
proptypeA: 'valA',
proptypeB: 'valB'
}
});
this.setState=({protype: 'xxxA}, _ => {
console.log('这里是异步回调');
});
插入富文本
render(){
return (
<div dangerouslySetInnerHtml = {__html:"<p>123</p>"}
);
}
获取DOM元素
react提供了ref来获取dom元素
//render部
<button ref="myButton" onClick={this.handleClick.bind(this)}>测试按钮</button>
//绑定的事件函数
handleClick = ()=>{
console.log(this.refs.myButton)
}
数据双向绑定
class login extends Component{
constructor(props){
super(props);
this.state = {
userName: ''
}
}
handleChange(e, event){
for(let item in this.state){
if(item == e){
this.state[item]=event.target.value;
this.setState(this.state)
}
}
}
render(){
return(
<div>
<input
type="text"
value={this.state.userName}
onChange={this.handleChange.bind(this, 'userName')}>
</input>
</div>
)
}
}
阻止事件冒泡
handleCoverClick = ()=>{
console.log('handle cover click!')
}
handleButtonClick = (param, e)=>{
e.stopPropagation() //阻止事件冒泡
console.log(param)
}
<div onClick={ this.handleCoverClick }>
<button onClick={ e => this.handleButtonClick('handle button click!', e) }></button>
</div>
动态绑定className
利用模板字符串实现
<div className={`classA ${a===b?'classB':''}`}><//div>
实现scoped即styled-components的使用
styled-components实现了使用js文件来处理css样式,同时也解决了组件间样式冲突的问题。
安装
npm i styled-components --s
样式文件
import styled from 'styled-components';
import picUrl from 'xxx/xx/xxx.png';
const HomeWrapper = styled.div `
background-color: red;
background-image: url(${picUrl}); // 引入图片
color: url(${props=>props.color}); // 使用组件传值
`;
export {
HomeWrapper
}
组件中使用
import React, Component from 'react';
import {HomeWrapper} from './styledHome';
class Home extends Component{
render(){
return(
<HomeWrapper color="#fff"></HomeWrapper>
)
}
}
其他详细内容移步styled-components.com/
父组件向子组件传参
//父组件
<ButtonUi type="primary" text="主要按钮"></ButtonUi>
//子组件
class buttonUi extends Component{
render(){
const {type, text} = this.props;
return(
<button className={`${type}`}>{text}</button>
)
}
}
子组件向父组件传参
//子组件
class buttonUi extends Component{
click(text){
let {click} = this.props;
if (click && typeof click === 'function') {
click(text);
}
}
render(){
return(
<button onClick={this.click.bind(this, '传递的参数')}></button>
)
}
}
//父组件
class father extends Component{
handleClick(text){
console.log('传递的参数是: ', text);
}
render(){
return (
<ButtonUi click={this.handleClick.bind(this)}></ButtonUi>
);
}
}
使用pubsub实现兄弟组件间传参
安装
npm i pubsub-js --s
使用
兄弟组件1
import Pubsub from 'pubsub-js';
class Test1 extends Component {
render() {
return (
<div>
<button onClick={()=>this.handleClick()}>test1</button>
</div>
)
}
handleClick(){
Pubsub.publish('emit', 'test1')
}
}
兄弟组件2
class Test2 extends Component {
constructor(props){
super(props);
this.state = {
name: 'Test2'
}
Pubsub.subscribe('emit', (msg, data)=>{
this.setState({
name: data
})
})
}
render() {
return (
<div>
<h1>{this.state.name}</h1>
</div>
)
}
}
后面会介绍react-redux,hook useContext来实现兄弟组件公用数据。
props验证与默认值
函数组件
import React from 'react';
import PropTypes from 'prop-types';
const Test = function(props){
return <div>{props.name}</div>
}
Test.defaultProps = {
name: '张三'
}
Test.propTypes = {
name: PropTypes.string,
msg: PropTypes.string.isRequired // 追加一个非空验证
}
类组件
import React, Component from 'react';
import PropTypes from 'prop-types';
class Test extends Component{
static defaultProps = {
name: '张三'
}
static propTypes = {
name: PropTypes.string
}
return <div>{props.name}</div>;
}
这里只做用法介绍,详情请参考
zh-hans.reactjs.org/docs/typech…
实现slot插槽功能
//父组件
import React, {Component} from 'react';
import './jbutton.scss';
import CodeUi from '../../components/codeUi/codeUi';
class jbutton extends Component{
render(){
return(
<div className="jbutton-container">
<CodeUi>
<div>自定义扩展内容</div>
</CodeUi>
</div>
);
}
}
export default jbutton;
//子组件
import React, { Component } from 'react';
import './codeUi.scss';
class codeUi extends React.Component{
render(){
return(
<div>
<div className="code-container">
<div className="icon">显示代码</div>
</div>
<div className="code-content">
{this.props.children.map((item, index)=>{
return (<div key={index}>{item.props.children}</div>)
})}
</div>
</div>
);
}
}
export default codeUi;
路由介绍
Router组件
Router组件是一个容器,路由是需要Route进行定义。
import React from 'react';
import {Router} from 'react-router';
const App = () => {
return (
<Router>
{/* .... */}
</Router>
);
};
export default App;
Route, 配合Router容器使用
import React from 'react';
import {Router, Route, browserHistory} from 'react-router';
import LayOut from 'xxx/Layout';
import Login from 'xxx/Login';
const App = () => {
return (
<Router history={browserHistory}>
<Route path='/' component={LayOut}></Route>
<Route path='/login' component={Login}></Route>
</Router>
);
};
export default App;
Redirect
作用是访问一个路由,会自动跳转到另一个路由。
import React from 'react';
import {Router, Route, Redirect, browserHistory} from 'react-router';
import LayOut from 'xxx/Layout';
import Login from 'xxx/Login';
import Main from 'xxx/Main';
const App = () => {
return (
<Router history={browserHistory}>
<Route path='/' component={LayOut}></Route>
<Route path='/login' component={Login}></Route>
<Route path='/main' component={Main}></Route>
<Redirect from='/main/index' to='/main/:id'></Route>
</Router>
);
};
export default App;
路由嵌套
访问/main/about时,会先加载Main组件,然后在它的内部再加载About组件。
import React from 'react';
import {Router, Route, Redirect, browserHistory} from 'react-router';
import Main from 'xxx/Main';
import Detail from 'xxx/Detail';
import About from 'xxx/About';
const App = () => {
return (
<>
<Router history={browserHistory}>
<Route path="/main" component={Main}>
<Route path="/detail" component={Detail}/>
<Route path="/about" component={About}/>
</Route>
</Router>
</>
);
};
export default App;
路由的钩子
可以使用onEnter来做认证。
const requireAuth = (nextState, replace) => {
if (!auth.isAdmin()) {
replace({ pathname: '/' })
}
};
export const AdminRoutes = () => {
return (
<Route path="/admin" component={Admin} onEnter={requireAuth} />
);
}
路由跳转和传参
跳转
this.props.history.push('/home/jtable');
传参
// 跳转时传参
this.props.hisroy.push({pathname: '/index/home', query:{id:'admin'}})
// 获取参数
this.props.location.query
路由信息获取
this.props.location.pathname
react-router + react-router-dom + react-router-config快速配置路由
安装依赖
npm install react-router --s
npm install react-router-dom --s
npm install react-router-config --s
创建配置文件
import login from '../pages/login/login';
import home from '../pages/home/home';
const routes = [
{
path: '/',
component: login,
exact: true
}
];
export default routes;
App.js根组件引入使用
这里使用了react-router-config第三方工具进行路由渲染,当然也可以自己实现高阶组件进行自定义路由渲染。
import React from 'react';
import './App.scss';
import { BrowserRouter } from 'react-router-dom';
import { renderRoutes } from 'react-router-config';
import routes from './router/index'
const App = () => {
return (
<div className="App">
<BrowserRouter>
{renderRoutes(routes)}
</BrowserRouter>
</div>
);
};
export default App;
配置路由嵌套
//路由配置
{
path: '/home',
component: home,
children:[
{
path: '/home/jtable',
component: jtable
},
{
path: '/home/jbutton',
component: jbutton
}
]
}
//嵌套页面
import React, {Component} from 'react';
import { Link } from 'react-router-dom';
import { renderRoutes } from 'react-router-config';
class home extends Component{
constructor(props){
super(props)
this.state = {
route: props.route.children
}
}
render(){
const route = this.state.route
return(
<div>
<h1>home!</h1>
<div>
<Link to="/home/jtable">jtable</Link>
</div>
<div>
{renderRoutes(route)}
</div>
</div>
)
};
}
export default home;
路由懒加载配置
//安装依赖
npm install react-loadable
//routes模块配置
import React from 'react';
import Loadable from 'react-loadable';
const Loading = function(){
return <div>loading...</div>;
};
const routes = [
{
path: '/',
component: Loadable({
loading: Loading,
loader:()=>import('../login/login')
}),
exact: true
},
{
path: '/home',
component: Loadable({
loading: Loading,
loader:()=>import('../home/home')
})
}
];
export default routes;
react-redux
//安装相关包,react-redux依赖于redux,redux和react-redux必须一起安装
npm install redux --save
npm install react-redux --save
目录结构
--store
--reducers // 自定义模块
--user.js
--common.js
--index.js // 整合store出口
--index.js
store/reducers/user.js
const initState = {
account: localStorage.getItem('account') || ''
};
const reducer = (state = initState, action) => {
switch(action.type){
case 'LOGIN':
localStorage.setItem('account', action.val)
return {...state}
case 'LOGOUT':
localStorage.removeItem('account')
return {...state}
default:
return {...state}
}
};
export default reducer;
/store/index.js模块导出
// createStore方法是用来创建store的,combineReducers方法是用来合并多个reducer的
import { createStore, combineReducers } from 'redux';
import userReducer from './reducers/user';
// 创建根reducer,利用combineReducers合并多个reducer,此处还未定义reducer,所以暂空
const rootReducer = combineReducers({
userReducer
});
// 创建初始化的state,初始化为一个空对象即可,默认的数据建议都写在reducer上
const initState = {};
// 创建store,第一个参数是根reducer,第二个参数可以是初始化的state,也可以是别的,暂且不提
const store = createStore(rootReducer, initState);
export default store;
/index.js全局引入
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { Provider } from 'react-redux';
import store from './store/index';
ReactDOM.render(
// 全局挂载
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
组件中使用
import React, {Component} from 'react';
import './login.scss';
import { Form, Button, Input, message } from 'antd';
import { UserOutlined, LockOutlined } from '@ant-design/icons';
// 引入connect
import { connect } from 'react-redux';
class login extends Component{
constructor(){
super()
this.state = {
form: {
account: '',
password: ''
}
}
}
// 渲染部
render(){
return(
<div className="login-container">
<div>{this.props.account}</div>
<div className="login-form">
<div className="form-header">
<h2>系统登录</h2>
</div>
<Form name="basic">
<Form.Item name="account" rules={[{ required: true, message: '请输入账户名' }]}>
<Input
size="large"
placeholder="请输入账户名"
prefix={<UserOutlined/>}
maxLength="20"
value={this.state.form.account}
onChange={this.onAccountChange}>
</Input>
</Form.Item>
<Form.Item name="password" rules={[{ required: true, message: '请输入密码' }]}>
<Input.Password
size="large"
placeholder="请输入密码"
prefix={<LockOutlined/>}
maxLength="20"
value={this.state.form.password}
onChange={this.onPasswordChange}>
</Input.Password>
</Form.Item>
<Form.Item>
<Button type="primary" onClick={()=> this.login()}>登录</Button>
</Form.Item>
</Form>
<div className="form-item form-bottom">
<div className="bottom-left">
<div className="left-item">账号:admin</div>
<div className="left-item">密码:admin1</div>
</div>
<div className="bottom-right">
<Button type="primary">第三方登录</Button>
</div>
</div>
</div>
</div>
)
}
// 登录
login = ()=>{
if(this.state.form.account != 'admin' || this.state.form.password != 'admin1'){
message.warning('账号或密码错误!')
return
}
this.props.Login(this.state.form.account)
this.props.history.push('/home')
}
//账号数据绑定
onAccountChange = ({target: {value}})=>{
this.setState(state=>{
return {
form: {
account: value,
password: this.state.form.password
}
}
})
}
// 密码数据绑定
onPasswordChange= ({target: {value}})=>{
this.setState(state=>{
return {
form: {
account: this.state.form.account,
password: value
}
}
})
}
}
// state映射
function mapStateToProps(state) {
return {
account: state.userReducer.account
}
}
// dispatch映射
function mapDispatchToProps(dispatch) {
return {
Login: e => {
dispatch({ type: 'LOGIN', val: e})
}
}
}
// 注意修改 除了这种写法也可使用装饰器
export default connect(mapStateToProps, mapDispatchToProps)(login);
react-saga使用方法
react-saga是帮助react-redux实现异步的一种解决方案。
界面提交action=>saga, saga接收action异步处理后再提交一个新的action=>reducer处理。
安装
npm i redux-saga --s
目录结构
--pages
--hotcat
--HotCat.jsx //界面
--store
--reducer
--common.js // reducer
--index.js // store整合导出文件
--sagas.js // saga文件
... // 此处省略
--index.js
HotCat.jsx界面
import React, { Component } from 'react';
import { connect } from 'react-redux';
class HotCat extends Component {
componentDidMount(){
this.props.setList();
}
render() {
return <div>hot cat</div>;
}
}
function mapStateToProps(state){
return{
list: state.common.list
};
};
function mapDispatchToProps(dispatch){
return{
setList: e=>dispatch({
type: 'INIT_LIST'
})
};
};
export default connect(mapStateToProps, mapDispatchToProps)(HotCat);
saga.js文件
import {
takeEvery, // 所有异步函数依次执行
takeLatest, // 短时间内执行多次异步操作只返回最先完成的任务,其他任务中止
throttle, // 类似节流 使用方法类似
select,
call,
put
} from 'redux-saga/effects';
import request from '../utils/request/index';
// 执行函数
const getInitList = params => {
console.log(params); // 输出this is a params
return request.get('/api/hotCatData')
}
// 代码生成器
const saga = function* initList() {
yield takeEvery('INIT_LIST', function* (e) {
// 获取state数据
let state = yield select(state => state.common);
// 调用函数
let result = yield call(getInitList, 'this is a params');
// 提交一个新的action给reducer
yield put({
type: 'INIT_SUCCESS',
val: result.data
})
});
}
export default saga;
common store文件
const initState = {
list: []
}
const reducer = (state=initState, action)=>{
switch(action.type){
// 接收action并区分处理
case 'INIT_SUCCESS':
state.list = action.val;
return {...state};
default:
return {...state, ...action};
}
}
export default reducer;
store/index.js整合配置redux-saga, 并导出reducer文件
import { createStore, combineReducers, applyMiddleware } from 'redux';
import createSagaMiddleWare from 'redux-saga';
import common from './reducer/common';
import sagas from './sagas';
// 获取redux-saga中间件实例
const sagaMiddleWare = createSagaMiddleWare();
const rootReducer = combineReducers({
common
})
// 把中间件注入到store中
const store = createStore(
rootReducer,
applyMiddleware(sagaMiddleWare)
);
// 执行sagas.js文件
sagaMiddleWare.run(sagas);
export default store;
/index.js全局使用store
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import {Provider} from 'react-redux';
import store from './store/index';
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root')
);
react-router + react-router-config + react-redux 实现登录拦截
目录结构
---login
---login.js //登录界面
---login.scss
---store
---reducers
---user.js //全局 user store
---index.js
---router
---index.js //路由配置文件
---renderRoutes.js //路由渲染函数
---App.js //根页面
登陆界面 (页面中包含ant-design, 请自行引入)
import React, {Component} from 'react';
import './login.scss';
import { Form, Button, Input, message } from 'antd';
import { UserOutlined, LockOutlined } from '@ant-design/icons';
import { connect } from 'react-redux';
class login extends Component{
constructor(){
super();
this.state = {
form: {
account: '',
password: ''
}
}
}
// 渲染部
render(){
return(
<div className="login-container">
<div className="login-form">
<div className="form-header">
<h2>系统登录</h2>
</div>
<Form name="basic">
<Form.Item name="account" rules={[{ required: true, message: '请输入账户名' }]}>
<Input
size="large"
placeholder="请输入账户名"
prefix={<UserOutlined/>}
maxLength="20"
value={this.state.form.account}
onChange={this.onAccountChange}>
</Input>
</Form.Item>
<Form.Item name="password" rules={[{ required: true, message: '请输入密码' }]}>
<Input.Password
size="large"
placeholder="请输入密码"
prefix={<LockOutlined/>}
maxLength="20"
value={this.state.form.password}
onChange={this.onPasswordChange}>
</Input.Password>
</Form.Item>
<Form.Item>
<Button type="primary" onClick={()=> this.login()}>登录</Button>
</Form.Item>
</Form>
<div className="form-item form-bottom">
<div className="bottom-left">
<div className="left-item">账号:admin</div>
<div className="left-item">密码:admin1</div>
</div>
<div className="bottom-right">
<Button type="primary">第三方登录</Button>
</div>
</div>
</div>
</div>
)
}
// 登录
login = ()=>{
if(this.state.form.account != 'admin' || this.state.form.password != 'admin1'){
message.warning('账号或密码错误!')
return
}
// ---重点, 登录时全局添加登录标识
this.props.Login(this.state.form.account)
this.props.history.push('/index/home') //跳转
}
//账号数据绑定
onAccountChange = ({target: {value}})=>{
this.setState(state=>{
return {
form: {
account: value,
password: this.state.form.password
}
}
})
}
// 密码数据绑定
onPasswordChange= ({target: {value}})=>{
this.setState(state=>{
return {
form: {
account: this.state.form.account,
password: value
}
}
})
}
}
function mapStateToProps(state) {
return {
account: state.userReducer.account
}
}
//---重点, react-redux映射函数
function mapDispatchToProps(dispatch) {
return {
Login: () => {
dispatch({ type: 'LOGIN' })
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(login);
user store (/store/reducers/user.js)
// 全局state
const initState = {
account: localStorage.getItem('account') || ''
};
// 全局reducer
const reducer = (state = initState, action) => {
switch(action.type){
// 登录
case 'LOGIN':
state.account = action.val;
localStorage.setItem('account', action.val);
return {...state};
// 登出
case 'LOGOUT':
state.account = '';
localStorage.removeItem('account');
return {...state};
default:
return {...state};
}
}
export default reducer;
store模组导出函数(/store/index.js)
// createStore方法是用来创建store的,combineReducers方法是用来合并多个reducer的
import { createStore, combineReducers } from 'redux';
import userReducer from './reducers/user';
// 创建根reducer,利用combineReducers合并多个reducer,此处还未定义reducer,所以暂空
const rootReducer = combineReducers({
userReducer
});
// 创建初始化的state,初始化为一个空对象即可,默认的数据建议都写在reducer上
const initState = {};
// 创建store,第一个参数是根reducer,第二个参数可以是初始化的state,也可以是别的,暂且不提
const store = createStore(rootReducer, initState);
export default store;
路由配置文件(/router/index.js)
import React from 'react';
import Loadable from 'react-loadable';
const Loading = function(){
return <div></div>;
};
const routes = [
{
path: '/',
component: Loadable({
loading: Loading,
loader: ()=>import('../pages/login/login')
}),
exact: true,
requiresAuth: false //---重点,是否需要权限
},
{
path: '/index',
component: Loadable({
loading: Loading,
loader: ()=>import('../pages/index/index')
}),
exact: false,
requiresAuth: true, //---重点,是否需要权限
children: [
{
path: '/index/home',
component: Loadable({
loading: Loading,
loader: ()=>import('../pages/home/home')
}),
exact: false,
requiresAuth: true //---重点,是否需要权限
},
{
path: '/index/doc',
component: Loadable({
loading: Loading,
loader: ()=>import('../pages/doc/doc')
}),
exact: false,
requiresAuth: true //---重点,是否需要权限
}
]
}
];
export default routes;
自定义路由渲染函数(/router/renderRoutes)
import React from 'react'
import { Route, Redirect, Switch } from 'react-router-dom'
//---重点,routes:路由配置信息,authed:是否有跳转权限, authPath:没有跳转权限时的强制跳转地址
const renderRoutes = (routes, authed, authPath = authPath, extraProps = {}, switchProps = {}) => routes ? (
<Switch {...switchProps}>
{routes.map((route, i) => (
let {key, path, exact, strict} = route;
<Route
key={key || i}
path={path}
exact={exact}
strict={strict}
render={(props) => {
if (!route.requiresAuth || authed || route.path === authPath) {
return <route.component {...props} {...extraProps} route={route} />
}
return <Redirect to={{ pathname: authPath, state: { from: props.location } }} />
}}
/>
))}
</Switch>
) : null
export default renderRoutes;
根页面(/App.js)
import React, { Component } from 'react';
import './App.css';
import { BrowserRouter } from 'react-router-dom';
//引入自定义路由渲染函数
import renderRoutes from './router/renderRoutes'
//react-redux连接函数
import { connect } from 'react-redux'
//路由配置文件
import routes from './router/index'
class App extends Component{
render(){
return (
//因为鉴权是动态的所以要动态赋值,三步式写在函数内部
<BrowserRouter>
{ renderRoutes(routes, this.props.account?true:false, '/') }
</BrowserRouter>
)
}
}
// 鉴权状态映射
function mapStateToProps(state) {
return {
account: state.userReducer.account
};
};
export default connect(mapStateToProps)(App);
使用Echarts图标渲染数据
安装
npm install echarts-for-react --save
npm install echarts --save
使用
import React, {Component} from 'react'
import './home.scss'
//按需加载
import echarts from 'echarts/lib/echarts'
//引入主题样式 可以从官网下载主题json文件作为js对象导出然后在这里引用
//官网地址: https://echarts.apache.org/zh/download-theme.html
import theme from './primary'
class home extends Component{
//要在dom元素加载完毕后初始化echarts表格
componentDidMount = ()=>{
let myEchart = echarts.init(this.refs.myEchart, theme) //初始化
myEchart.setOption(this.getOption()) //数据设定
}
getOption =()=> {
let option = {
title:{
text:'用户骑行订单',
x:'center'
},
tooltip:{
trigger:'axis',
},
xAxis:{
data:['周一','周二','周三','周四','周五','周六','周日']
},
yAxis:{
type:'value'
},
series:[
{
name:'OFO订单量',
type:'line', //这块要定义type类型,柱形图是bar,饼图是pie
data:[1000,2000,1500,3000,2000,1200,800]
}
]
}
return option
};
render(){
return(
<div>
<div ref="myEchart" style={{width:800, height:400}}></div>
</div>
);
}
}
export default home;
函数组件
import React from 'react;
const App = props => {
return <div>这是一个组件</div>;
};
export default App;
函数组件与类组件的区别
函数组件与类组件返回的都不是html无法直接对其进行渲染,其本质返回的都是与dom对应的对象,页面根据这个dom对象进行实际渲染。
函数组件使用时无需像类组件一样需要实例化,所以尽量使用函数组件来提高性能。 类组件则相反,使用时需要实例化。
| 区别 | 函数组件 | 类组件 |
|---|---|---|
是否有this | 没有 | 有 |
| 是否有生命周期 | 没有 | 有 |
是否有状态state | 没有 | 有 |
Hooks
hooks解决了函数组件无状态的问题,使得函数组件也可以像类组件一样的使用。
useState
让函数组件可以使用状态数据。
import React, { useState } from 'react';
function example(){
const [count, setCount] = useState(0)
return (
<p>{count}</p>
<button onClick={()=>{
setCount(count + 1)
}}></button>
)
}
useEffect
useEffect就好比componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个生命周期的组合。
结合useState举例。
import React, {useState, useEffect} from 'react';
const App = props => {
const [count, addCount] = useState(0);
useEffect(() => {
addCount(count + 1);
});
};
export default App;
执行后会出现无限循环的情况。因为useEffect在组件mount时或者组件更新时执行, 如果只想在组件mount时请求数据,可以传递一个空数组作为useEffect的第二个参数。
useEffect(() => {
addCount(count + 1);
}, []);
除此之外,useEffect的第二个参数可用于定义其依赖的所有变量,如果其中有变量发生变化,那么useEffect会再次运行。
import React, {useState, useEffect} from 'react';
const App = props => {
const [bean, setBean] = useState(false);
const [count, addCount] = useState(0);
useEffect(() => {
addCount(count + 1);
}, [bean]);
return (
<>
<h1>{count}</h1>
<button onClick={setBean(!bean)} />
</>
);
};
export default App;
如果想在useEffect中执行异步操作呢?
import React, {useState, useEffect} from 'react';
import axios from 'axios';
const App = props => {
const [count, setCount] = useState(count);
useEffect(async () => {
let res = await axios.get('http://xxxxx');
setCount(res);
}, []);
return <h1>{count}</h1>
};
export default App;
如上这种写法是会报错的,因为async, await返回的是一个promise对象,而useEffect的第一个参数接收的是一个函数,所以可以用如下这种写法;
// ...
useEffect(() => {
const toDo = async () => {
let res = await axios.get('http://xxx');
setCount(res);
};
toDo();
}, []);
再看一下如何在useEffect中使用并清除定时器。
useEffect(() => {
const timer = setInterval(() => {
console.log(new Date().getTime());
}, 1000);
return () = {
clearInterval(timer);
};
}, []);
useMemo
useMemo和useEffect很像,区别在于,useEffect可以处理副作用,也就是说如果只想在依赖变更时执行逻辑,那么就可以使用useMemo。
注意: 不要在useMemo中修改state数据,因为useMemo是在渲染中进行的, 否则会出现死循环现象。
import React, {useState, useEffect, useMemo} from 'react';
const App = props => {
const [count, setCount] = useState(0);
const [name, setName] = useState('李四');
const getNameByNormal = () => {
console.log('get name by normal');
return `${name} normal`;
};
useEffect(() => {
console.log('count effect');
}, [count]);
const getNameByMemo = useMemo(() => {
console.log('get name by memo');
return `${name} meo`
}, [name]);
return (
<>
<h1>{getNameByNormal()}</h1>
<h1>{getNameByMemo()}</h1>
<button onClick={setCount(count + 1)}>add count</button>
<button onClick={setName('张三')}>set name</button>
</>
);
};
export default App;
点击add count按钮时,打印get name by normal, count effect。
点击set name按钮时,打印get name by memo, get name by normal。
使用方法如下
const [name, setName] = useState('');
const getName = useMemo(() => {
return `${name} memo`;
}, [name]);
return (
<>
<h1>{getName}</h1>
<button onClick={setName('李四')}set name</button>
</>
);
useContext
让函数组件间共享状态
import React, {createContext, useContext} from 'react';
const StoreContext = createContext({});
const state = {
name: '张三',
age: '20'
};
// 根组件
function App(){
return (
{/* 注入全局数据 */}
<StoreContext.Provider value = {state}>
</StoreContext.Provider>
)
}
// 子组件1
function Child1(){
const {name, age} = useContext(StoreContext);
return (
<ul>
<li>name</li>
<li>age</li>
</ul>
)
}
// 子组件2... 一样可以以这种方式共享数据
useReducer
看了useContext的使用场景后,发现hooks也许可以实现react-redux的数据处理方式。在用useContext实现了全局状态共享后,再使用useReducer实现组件内部的数据提交。
import React, {createContext, useContext, useReducer} from 'react';
const StoreContext = createContext({});
const initState = {
name: '张三',
age: 20
}
function rootReducer(state, action){
switch(action.type){
case 'CHANGE':
return {
name: action.val,
...state
}
default:
return state
}
}
// 根组件
function App(){
const [state, dispatch] = useReducer(rootReducer, initState);
return (
{/*全局注入state和dispatch*/}
<StoreContext.Provider value = {{
state,
dispatch
}}>
</StoreContext.Provider>
)
}
// 子组件1
function Child1(){
const {state, dispatch} = useContext(StoreContext);
handleClick = ()=>{
dispatch({
type: 'CHANGE',
val: '李四'
});
}
return (
<div>
<h1>{state.name}</h1>
<button onClick = {handleClick}>dispatch</button>
</div>
)
}
同样的,如果想实现一个异步操作那么在提交action时,在负载payload中添加一个promise异步函数,然后在提交action时执行payload中的异步函数,执行后再dispatch一个action给reducer即可。
如此一来hooks配合函数组件基本就实现了`react-redux的功能。
useRef
可以用来获取dom对象, 或者跨渲染周期保存数据。
import React, { useState, useEffect, useMemo, useRef } from 'react';
const App = props => {
const [count, setCount] = useState(0);
const couterRef = useRef(); // 保存dom
const customRef = useRef(100); // 保存数据
const addCount = useMemo(() => {
return count + 1;
}, [count]);
useEffect(() => {
console.log(`dom value is ${couterRef.current.target.value}`);
}, [count]);
return (
<>
<button
ref={couterRef}
onClick={() => {setCount(count + 1)}}
>
Count: {count}, addCount: {addCount}
</button>
</>
);
};
export default App;
使用装饰器decorators
安装
npm i @babel/plugin-proposal-decorators --s
config.overrides.js中配置
const { override, fixBabelImports, addWebpackAlias, addDecoratorsLegacy } = require('customize-cra');
module.exports = function override(config, env) {
return config;
};
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd-mobile',
style: 'css'
}),
addDecoratorsLegacy()
);
在组件中配合react-redux使用
import React, { Component } from 'react';
import { HotCatMain } from './styleCookBook';
import { connect } from 'react-redux';
// @connect(mapStateToProps, mapDispatchToProps)
// 或者把map函数直接写到connect装饰器中来
@connect(
state => ({
list: state.common.list
}),
dispatch => ({
setList(e) {
dispatch({
type: 'INIT_LIST',
val: e
})
}
})
)
class HotCat extends Component {
componentDidMount() {
this.props.setList();
}
render() {
return (
<HotCatMain>
{
this.props.list.map(item => {
return (
<div key={item.id}>
<img src={item.picUrl}></img>
<span>{item.name}</span>
</div>
)
})
}
</HotCatMain>
)
}
}
// function mapStateToProps(state){
// return{
// list: state.common.list
// }
// }
// function mapDispatchToProps(dispatch){
// return{
// setList: e=>dispatch({
// type: 'INIT_LIST'
// })
// }
// }
export default HotCat;