React
概念:
数据单向流
脚手架使用:
安装
npx i -g create-react-app
创建项目
create-react-app <项目名>
目录结构:
- 加载页面
public\index.html
- 入口文件
src\index.js
主要的三个包:
- react 引入React对象
- react-dom 实现页面挂载
组件:
函数组件(无状态组件):
没有this, his—>undefined
//函数组件
// const Node = ()=>{
// return (
// <div>
// <h1>我是函数组件</h1>
// </div>
// )
// }
ReactDOM.render(
Node(),
document.getElementById('root')
)
类组件(状态组件):
this指向实例
// 类组件--状态组件
// 先定义一个普通类,然后继承Component类,此类就为一个类组件
// 在继承过来的React.Component类中有一个必须实现的render方法
// class Node extends React.Component{
// render(){
// return(
// <div style={{width:500,height:500,backgroundColor:'red',}}>
// <h1>我是类组件</h1>
// </div>
// )
// }
// }
ReactDOM.render(
<Node/>,
document.getElementById('root')
)
数据绑定
语法:{}
import React, { Component } from 'react'
export default class Index extends Component {
fun(){
console.log('fun函数');
console.log(this);
}
fun1(){
console.log('fun1函数');
console.log(this);
}
fun2(){
console.log('fun2函数');
console.log('this:',this);
console.log('arguments',arguments);
}
render() {
return (
<div>
<p onClick={()=>{
console.log('this:',this);
}}>点击事件-直接写在标签内'this-Index组件'</p>
<hr/>
<button onClick={this.fun}>render外部的方法,this.函数名,函数内部this是undefined</button>
<hr/>
<button onClick={this.fun1.bind(this)}>render外部的方法,使用bind(this),改变this指向-Index</button>
<hr/>
<button onClick={this.fun2.bind(this,new Date(), 'today', 21)}>传值方法</button>
</div>
)
}
}
遍历渲染:
import React, { Component } from 'react'
export default class mapRender extends Component {
render() {
//定义一个空数组
let arr = []
//向空数组中添加10条数据
for (let i = 0;i<10;i++){
arr.push(
<li key={i}>
<span>我的待办事项-{i+1}</span>
<span>状态:未处理</span>
</li>
)
}
return (
<div>
<h1>待办事项列表</h1>
<hr/>
<ul>
{ arr }
</ul>
<hr/>
<h1>数组的遍历渲染2</h1>
<ul>
{
//定义一个长度为10的空数组。并填充值
Array(10).fill({title:'我的待办事项--',state:'完成状态:'}).map((v,i)=>{
//定义一个变量保存value 的值,并改变
let item = v.title+(i+1)
return(
<li key={i}>
<span>{item}</span>
<span>{v.state}</span>
</li>
)
})
}
</ul>
<hr/>
<h1>条件渲染</h1>
{<Tj/>}
<hr/>
<h1>父传子</h1>
{<FZ/>}
</div>
)
}
}
条件渲染:
条件渲染和状态管理
import React, { Component } from 'react'
//条件渲染
export default class conditionRender extends Component {
state={
//定义一个参照值flag=true
flag : false
}
change(){
console.log('this:',this);
this.setState({
flag:!this.state.flag
},
)
}
render() {
//定义一个参照值flag=true
// let flag = true
return (
<div>
<hr/>
{
this.state.flag
?
<h2> flag = true</h2>
:
<h2>flag = false</h2>
}
{/* <button onClick={
()=>{
this.change()
}
}>改变flag的值</button> */}
<button onClick={this.change.bind(this)}>改变flag的值</button>
</div>
)
}
}
事件绑定:小驼峰命名οnclick=>onClick
数据类型检测:
安装:npm i -s prop-types
父子组件:
父传子:
import React, { Component } from 'react'
//父组件
import Son from './day02/son'
export default class father extends Component {
state={
lilu:0.4
}
handel(){
this.setState({
lilu:(Math.random()).toFixed(3)
})
}
render() {
return (
<div>
//传递给子组件,若是方法注意改变this指向
{<Son lilu={this.state.lilu} change={this.handel.bind(this)}/>}
</div>
)
}
}
import React, { Component } from 'react'
//子组件
export default class son extends Component {
state={
//贷款金额
num:10000
}
render() {
console.log('fghjk:',this);
return (
<div>
<h1>贷款金额:{this.state.num}</h1>
<hr/>
<h1>当前利率:{this.props.lilu}</h1>
<hr/>
<h1>当前利息:{this.state.num*this.props.lilu}</h1>
<hr/>
//父组件改变了this指向过后,在子组件的props中可直接使用父组件的方法
<button onClick={this.props.change}>改变利率</button>
</div>
)
}
}
生命周期(类组件)
react 16.8 之后才有函数组件
挂载阶段
-
constructor() 用于定义state 已不常用
-
特殊的 getDerivedStateFromProps ()
-
render() 渲染生命周期,在更新的时候会执行
- render中的setSate不会合并操作,不建议在render中执行setSate
-
componentDidMount() 挂载完成的生命周期,常用于初始化数据
更新阶段
- shouldComponentUpdate():通常在性能优化的时候使用,此生命周期是在更新之前执行,因此可用于页面渲染优化,它需要返回一个Boolean值,为true则进行更新,反之则不更新
- render()
- **componentDidUpdate() **: 更新后立即调用,如:设置一个盒子的高度,然后去动态获取盒子的高度的方法可以在这个生命周期中执行
销毁
- componentWillUnmount():销毁前调用
函数组件与HOOKS
函数组件:
没有this,没有state
HOOKS:
-
在函数组件中可以使用该 hook 创建类似于 class 组件中的 state
const [state, setState] = useState(initialValue)
- useState() 返回的是数组,数组第一个元素为状态,第二个元素为修改状态的函数,initialValue 为状态初始值
-
useEffect 副作用函数 – 它可以用于监听数据变化,来做一个事情,类似Vue中的watch属性;它还可以实现类组件的生命周期(componentDidMount、componentWillUnmount)的功能
1)如果只传入一个方法参数,相当于监听所有的 state 数据
2)第二个参数为一个数组(React.DependencyList),在数组中传入需要监听的数据,这样的话,只有被监听的数据发生变化,才会执行函数方法``` 第一个参数为 callback 回调函数,即执行副作用操作的函数 第二个参数为依赖项的数组 `useEffect()` 相当于是 class 组件中 `componentDidMount()`、`componentDidUpdate()`、`componentWillUnmount()` 钩子函数的结合。 第二个参数为空数组,相当于模拟的是 `componentDidMount()` 第二个参数数组中有依赖项,相当于模拟的是 `componentDidUpdate()`,即依赖项发生变化,会重新执行 callback 回调函数 第一个参数 callback 回调函数中如果有返回一个函数,该返回的函数相当于模拟的是 `componentWillUnmount()` ```
-
useMemo 监听数据变化,必须返回一个新数据,类似Vue中的computed属性
1)使用的时候,两个参数必须全部传递
2)第一个参数为一个回调方法,必须有返回值
3)第二个参数为依赖数据项useMemo方法接收工厂函数,() => any 这是一个函数,并且这个函数需要有一个返回值,它的依赖项一定要写,并且这个方法会返回一个值 // 它相当于Vue的computed属性,它会根据依赖项的变化来执行回调函数,更新数据并缓存数据 const newFullName = useMemo(function() { return firstName + ' -- ' + secondName }, [firstName, secondName])
-
useCallback 用于缓存定义的方法,避免函数组件在执行更新的时候对同一个方法进行多次定义
// 在函数组件中可以使用useCallback来定义和缓存方法
// useCallback(callback: (...args: any[]) => any, deps: React.DependencyList): (...args: any[]) => any
// 用于定义方法
// 依赖为定义的方法需要使用到的数据,当数据发生变化的时候,方法会被更新
const clickEvt = useCallback(function() {
console.log(arguments);
console.log('useCallback: ', firstName, secondName);
}, [firstName, secondName])
Context:
a、使用 createContext 方法创建一个 Context 对象
b、在需要进行数据传递的地方使用 Context.Provider 组件 value 进行数据下发
c、在需要使用数据的地方使用 Context.Consumer (注意点:此处的Consumer必须与Provider成对使用)组件的回调方法,所有的数据在回调方法的入参对象中,来进行数据消费、使用
d、因为React中,所有的数据更新,会触发组件更新的数据只有 state 数据
import React, { Component, createContext } from 'react'
// Context在使用的是偶一定要注意:Provider和Consumer必须成对使用
const Context = createContext()
// 导出当前Context的Consumer方法
export const Consumer = Context.Consumer
......省略
//函数内部的render()
render() {
let store = {
list: this.state.list,
handleMethod: this.handleMethod
}
return (
// 在React中如果一个对象有一个组件,可以使用JS的方法来进行组件编写
// Provider必须有一个value属性
<div>
<Context.Provider value={ store }>
<div>
<h1>待办页面</h1>
<Form/>
<List list={this.state.list}/>
</div>
</Context.Provider>
</div>
)
}
或者单独提出来写
//创建一个context.js文件
//引入createContext
import { createContext } from 'react'
// 创建 Context 对象
const TodoContext = createContext()
// 解构组件
const {
Provider: TodoProvider, // 生产者,主要用于保存数据
Consumer: TodoConsumer, // 消费者,主要用于使用数据
} = TodoContext
// const TodoProvider = TodoContext.Provider
// 导出
export {
TodoProvider,
TodoConsumer,
TodoContext
}
//使用时
在APP文件中使用
<TodoProvider value={{
todos: this.state.todos,
add: this.addTodoItem,
toggle: this.toggleTodoItem,
remove: this.removeTodoItem
}}>
被包裹的组件都可以使用TodoConsumer来接收TodoProvider的值来使用
</TodoProvider>
包裹,
在Item中使用
return (
<TodoConsumer>
{
// 在使用Consumer的时候,所有在对应的Provider上提供的数据,都将在组件的回调方法中进行入参
// Consumer只能在对应的Provider中使用
arg => {
console.log(arg);
return (
<button onClick={
() => {
arg.toggle()
}
}>切换</button>
<button onClick={
() => {
arg.remove()
}
}>删除</button>
)
}
}
</TodoConsumer>
)
!!!!! connect !!!!
上面的Consumer使用过于麻烦,所以react-redux中提供了connect高阶组件,将数据和方法挂载到齐props上
// connect实现了两个功能:跨组件传值(实现了Context.Consumer的功能),
//还实现了Redux.subscribe的功能
import { connect } from 'react-redux'
const mapStateToProps = state =>{
return {
count:state.count
}
}
// connect(mapStateToProps,mapDispatchToProps)
// 参数1:数据 参数2:方法
export default connect(mapStateToProps)(Index)
HOC:高阶组件
- HOC及CRA中装饰器模式的写法支持
a、HOC–高阶组件、全称High Order Component,一个方法返回一个组件即为一个高阶组件,它可以实现类似于Vue的插槽功能,只是这个组件只能是顶层组件;它原由高阶函数的思想来实现的,主要是在开发业务的时候,需要每一个组件都是一个纯功能组件,也就是每一个组件只实现具体某一个功能,类似纯函数(slice、map、filter、concat);它主要的思想是:功能柯里化、业务解耦
b、HOC组件的使用方法,在使用的页面把自己的组件作为HOC组件的方法参数,并得到一个功能增强的组件
c、CRA的第三方配置库react-app-rewired使用
c0、安装:cnpm i -D react-app-rewired
c1、修改package.json文件的命令
c2、添加config-overrides.js文件
c3、添加自定义重装webpack配置库:cnpm i -D customize-cra
c4、在配置文件中添加:override、addDecoratorsLegacy库用于支持ES6装饰器的语法,提高HOC组件的可读性
Redux
Redux 是 JavaScript 应用的状态容器,提供可预测的状态管理。
Redux 是一个小型的独立 JS 库,redux 与 react 没有任何的关系,Redux 除了和 React 一起用外,还支持其它界面库。
要在 react 中使用 redux,需要安装绑定库:react-redux
概念
- store:仓库,用于集中式管理 redux 中的状态
- state:需要共享管理的状态数据
- action:描述发生了什么,是一个普通对象,通常具有 type(动作的类型) 与 payload(动作所携带的有效载荷) 属性,如:
{
type: 'ADD_TO_CART',
payload: {
id: 1,
title: '标题',
price: 9.9,
amount: 1,
checked: false
}
}
- action creator:函数,用于创建并返回 action 对象
- reducer:纯函数,主要用于同步更新状态。该函数会传递 state 与 action 作为参数,返回新的 state 值。reducer 函数是唯一能够更新 state 状态的地方。
纯函数:一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用,我们就把这个函数叫做纯函数
Reducer 必需符合以下规则:
1. 仅使用 state 和 action 参数计算新的状态值
2. 禁止直接修改 state。必须通过复制现有的 state 并对复制的值进行更改的方式来做 不可变更新(immutable updates)。
3. 禁止任何异步逻辑、依赖随机值或导致其他“副作用”的代码
- dispatch:函数,传递 action 作为参数,在函数体内部调用 reducer(state, action) 实现状态更新,并执行一些其它注册的监听任务(如响应式渲染刷新页面)。
使用
安装
$ npm i redux
# 或
$ yarn add redux
创建目录
store 目录用于保存所创建 store 对象相关
reducers 目录用于保存同步更新状态的 reducer 函数相关
actions 目录用于保存 action 对象(action creator创建)相关
reducer
const initialState = {
}
export default (state = initialState, { type, payload }) => {
switch (type) {
case typeName:
return { ...state, ...payload }
default:
return state
}
}
如果应用中有多种类别的状态数据需要管理,可定义不同的 reducer 函数来处理,最后将各独立的 reducer 合并为一个根 reducer 即可:
import { combineReducers } from 'redux'
import todosReducer from './todos'
import usersReducer from './users'
export default combineReducers({
todos: todosReducer,
users: usersReducer
})
store
import { createStore } from 'redux'
import rootReducer from '../reducers'
const store = createStore(rootReducer)
export default store
action creator
import { ADD_TODO_ITEM } from "./constants";
export const addActionCreator = title => ({
type: ADD_TODO_ITEM,
payload: {
id: Math.random(),
title,
completed: false
}
})
安装 react-redux
$ npm i react-redux
保存 redux 的 store
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import store from './store'
import App from './App'
import 'bulma/css/bulma.min.css'
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
利用 react-redux
库中提供的 Provider
组件,缓存 store
在组件中连接 store
import { connect } from 'react-redux'
// 函数,用于将 store 中的状态数据映射到组件中
// 函数返回值是一个对象,该对象会被合并到组件的 props 中
const mapStateToProps = state => ({
todos: state.todos.todos
})
// 函数,用于 dispatch (触发) action 调用 reducer 更新状态
const mapDispatchToProps = null
// connect() 调用后返回一个函数,该函数为 HOC
const hoc = connect(mapStateToProps, mapDispatchToProps)
export default hoc(TodoList)
const mapStateToProps = null
// 函数的返回值是一个对象,会被合并到组件的 props 中
const mapDispatchToProps = dispatch => ({
toggle: id => dispatch(toggleActionCreator(id)),
remove: id => dispatch(removeActionCreator(id))
})
export default connect(mapStateToProps, mapDispatchToProps)(TodoItem)
异步 Action
使用 redux-thunk 中间件
安装:cnpm i -S redux-thunk
import { createStore, applyMiddleware } from 'redux'
import reducer from './reducer'
import thunk from 'redux-thunk'
// createStore(reducer: Reducer, enhancer?: StoreEnhancer)
// 在使用中间件的时候,一定要用一个applyMiddleware方法来进行注入
export default createStore(reducer, applyMiddleware(thunk))
react-redux
API
Provider 组件:
使得所有嵌套在内部的后代组件都可以访问到保存在 Provider 中的 redux 中 store内容,使用 store
属性去缓存 redux 的 store 对象。
connect() 方法:
使用 connect() 方法来连接 react 组件与 redux 的 store
function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)
- mapStateToProps,函数,传递 state 参数,返回一个对象,主要用于处理 redux 的 store 中的 state。任何时间 store 中的 state 修改了,都会重新计算返回值。返回值(对象)会被合并到组件的 props 中。
mapDispatchToProps
,函数,传递 dispatch 参数,返回一个对象,主要用于处理 redux 中 store 的 dispatch。通常返回的对象中包含的就是一个个的方法,各方法体内部实现dispatch(action)
操作,返回的对象会被合并到组件的 props 中
React路由
安装
npm i -S react-router-dom
组件使用
-
HashRouter:创建 hash 路由模式
-
BrowserRouter:history 路由模式
-
Route:配置静态路由,访问 path 路径时渲染对应的 component 组件
<Route path="" component={} render={}>{children}</Route>
- path:URL 路径
- component:待渲染的组件
- render:渲染函数,可用于复杂结构的渲染,component 与 render 不能同时出现,如果同时出现,会忽略 render 的渲染
- children:类似于 render 实现复杂结构的渲染,是 react 元素结构
动态传参
- 动态路由,在定义路由的时候在路由字符串上添加(/:paramsName)属性,在页面上使用props.match.params.paramsName来接收参数;
- 在页面上的history.push()方法中传递一个对象,{pathname: 路由名字, state: {key-value形式来传递参数}},在页面上使用props.location.state对象来接收数据;
- 在Route中使用render方法,routeParams => RouteComponent,对应的数据在页面上使用props直接接收;
{/* 动态路由,在页面使用的时候,需要在match.params对象中获取数据 */}
<Route path="/login/reg/:id" component={Register}/>
路由匹配时:需要使用精确匹配模式 => exact
路由跳转
{/* 用它来实现默认路由跳转 */}
<Redirect path="/" to="/login" exact/>
{/* 把Redirect认为是一个default分支 */}
{/* 如果用户输入的路由在系统中不存在 */}
<Redirect to="/404"/>
{/* Lnik跳转 */}
<Link to="/home">登录</Link>
路由权限
<Route path="/home" render={
routeProps => {
// routeProps => location,match,history
// 对home路由进行拦截,直接返回到login路由
// sessionStorage中去去一个数据,如果数据存在进入页面,否则直接跳转到'/login'
let token = sessionStorage.getItem('x-token')
return (
!token
?
<NotRight/>
:
<Home value="10" {...routeProps}/>
)
}
路由高阶组件
withRouter():高阶组件,可访问 history 对象的属性和最近的 ,当路由渲染时,withRouter会将已更新的match、history、location属性传递给被包裹的组件
项目搭建
-
脚手架构建结构 creact-react-app <项目名>
- 建立入口文件src\index.js
-
建立 Views 视图文件夹
- APP.js
-
建立通用的样式文件
-
安装less解析器:
cnpm i -D less less-loader@7
-
定义用户爱好的样式
const [style, setstyle] = useState(false) return ( <div className={style ? "样式名1":"样式名2"}> 入口文件汇总处 </div> )
-
-
eject webpack 配置:
- 安装:
cnpm i -D react-app-rewired customize-cra
,自定义脚手架加载器 - 修改配置文件
package.json -> scripts -> build、start命令 react-scripts -> react-app-rewired
- config-overrides.js文件
const { override, addLessLoader } = require('customize-cra') module.exports = override( addLessLoader() )
- 安装:
-
全局状态数据管理,建立store文件:
-
安装:
npm i -s redux
-
创建 store\index.js 文件
import { createStore, applyMiddleware } from 'redux' import thunk from 'redux-thunk' import RootReducer from './reducer' export default createStore(RootReducer, applyMiddleware(thunk)) //applyMiddleware,thunk 处理一步action
-
创建 store\reducer\index.js
import { combineReducers } from 'redux' //combineReducers 合并导出reducer import common from './commonReducer'//方法 //combineReducers接收对象 export default combineReducers({ common })
-
创建store\reducer\commonReducer.js
import { SET_TOKEN, SET_USERINFO } from '../action/types' const initState = { token: 'init token', userInfo: {} } export default function(state = initState, action) { switch (action.type) { case SET_TOKEN: return { ...state, token: action.token } case SET_USERINFO: return { ...state, userInfo: action.userInfo } default: return JSON.parse(JSON.stringify(state)) } }
-
创建 store\action\types.js ,用于定义reducer方法中的 type 值
//标准写法 //symbol => Symbol用于生成项目全局的一个唯一值,它的参数只接收string和number类型数据 export const SET_TOKEN = Symbol('type') export const SET_USERINFO = Symbol('type')
-
创建 store\action\index.js 异步方法操作
import { SET_TOKEN, SET_USERINFO } from './types' export function tokenAct(token) { return {type: SET_TOKEN, token} } export function userinfoAct() { return async dispatch => { let userInfo = await asyncFunc() dispatch({type: SET_USERINFO, userInfo}) } } function asyncFunc() { return new Promise(resolve => { console.log('------------------ promise start'); setTimeout(() => { console.log('------------------ promise end'); resolve({id: 'admin', name: '超级管理员'}) }, 1500) }) }
-
-
withRouter: 把不是通过路由切换过来的组件中,将react-router 的 history、location、match 三个对象传入props对象上
-
路由懒加载(拆包)
安装:
cnpm i -S react-loadable
使用:
function myLoader(loader) { return loadable({ loader, loading: function() { return ( <div>Page Loading...</div> ) } }) } const Login = myLoader(() => import("./login"))
-
api接口
-
请求发送
1、安装依赖包:
cnpm i -S axios
2、实现axios的基本封装:
// 引入 axios 库 import axios from 'axios' // 设置开发环境与生产环境下 baseURL // const baseURL = process.env.NODE_ENV === 'development' ? '开发环境下的模拟接口' : '生产环境的真实接口' // const baseURL = process.env.NODE_ENV === 'development' // ? 'http://rap2api.taobao.org/app/mock/284783' // 开发环境 // : 'http://www.xiongmaoyouxuan.com' // 生产环境 const baseURL = 'http://www.xiongmaoyouxuan.com' // 创建 axios 实例 const service = axios.create({ baseURL, }) // 请求拦截 service.interceptors.request.use(config => { let token = 'my token' if (!token) { return Promise.reject({code: 0, message: '没有通过请求拦截'}) } else { config.headers.token = token return config } }) // 响应拦截 service.interceptors.response.use(config => { // 有响应数据返回,则关闭加载提示 Toast.clear() // 判断响应返回结果 if (config.status >= 200 && config.status < 300) { // 获取到后端返回数据 const resData = config.data // 判断 if (resData.code === 200) { return resData.data } else { const e = new Error('接口 code 不为 200') e.error = resData Promise.reject(e) } } else { const err = new Error('后端接口返回 status 不为 200') err.error = config Promise.reject(err) } }) // 导出 axios 实例 export default service
-
实现请求代理:
1、安装依赖包:
cnpm i -D http-proxy-middleware
2、新建代理文件:src/setupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware') module.exports = app => { app.use( '/apis', createProxyMiddleware({ //代理的目标地址 target: 'http://www.shuiyue.info:14600', //路径重写 pathRewrite: {'/apis': ''}, changeOrigin: true }) ) }
-
补充:
set()
var a = [1,2,3,4,5,3,2,4,1]
new Set(a)//得到的是一个对象
Array.from(new Set(a))
注:只能用于纯数组,若包含对象则不能去除重复对象
Symbol()可生成全局唯一值
var a = symbol(1)
var b = symbol(1)
a!=b