//parent components
export default class Parent extends Component {
render(){
return <div>
Parent
{this._render()}
</div>
}
}
//children component
export default class Children extends Parent {
_render(){
return <div>
Children
</div>
}
}
//最终结果
<div>
Parent
<div>
Children
</div>
</div>
高阶组件是参数为组件,返回值为新组件的函数。
//HOC
import React, { Component } from 'react'
export function ho(Comp) {
return class extends Component {
render(){
return <div className={['item', Comp.classify]}>
<Comp />
我被高阶组件包裹
</div>
}
}
}
//调用高阶组件
import React, { PureComponent } from 'react'
import {ho} from './ho'
class List extends PureComponent {
// 通过给组件加静态属性, 可以调用高阶组件时自动生成类名
static classify = 'list'
render() {
return (
<div>
多图组件
</div>
)
}
}
export default ho(List)
//hoc
import React, { Component } from 'react'
export function ho(Comp) {
return function(clickAble = true){
return class extends Component {
render(){
return <div onClick={clickAble ? this.props.onClick : ()=>{}} className="comp">
<Comp />
我被高阶组件包裹
</div>
}
}
}
}
// 调用包装组件
//该组件无法点击
import React, { PureComponent } from 'react'
import {ho} from './ho'
//也可以用装饰器
// @ho(false)
class List extends PureComponent {
render() {
return (
<div>
单图组件
</div>
)
}
}
export default ho(List)(false)
异步组件本质是promise 最后return resolve(component)
const List = React.lazy( () => import('./components/List'))
export class extends React.components {
render(){
return <React.Fragment>
<React.Suspense fallback={<div>loading...</div>}>
<List />
</React.Suspense>
</React.Fragment>
}
}
//context.js
export default React.createContext({
defVal: 'xxx'
})
//parent.jsx
import context from './context.js'
import Children from './Children'
export default class extends React.Component {
render(){
return <div>
<React.Provider value={['tom', 'jack']}>
<Children />
</ React.Provider>
</ div>
}
}
//childred.jsx
import tabContext from './context.js'
export default class extends React.Component {
//将需要使用conetxt的组件内声明static属性 context
static context = tabContext
render(){
return <React.Fragment>
//使用context值
{this.context.xxxx}
</ React.Fragment>
}
}
import React, {useState} from 'react'
function FuncComponent(props) {
let [price, setPrice] = useState(10)
useEffect(()=>{
//更新时调用
return () => {
//销毁调用
}
})
return (<>
<h3 onClick={() => setPrice(100)}> {price} </h3>
</>)
}
export default class App extends React.Component {
render(){
return <FuncComponent>
}
}
redux底层state更新后直接替换currentState对象 subscribe订阅会将回调插入listers数组 dispatch时先执行reducer 然后遍历listers数组执行函数
redux响应式更新数图 订阅action
react-redux 是用context.Provider实现所有后代共享数据
// store.js
import { createStore, applyMiddleware } from 'redux'
const listProcessor = (state, action) => {
console.log('state-action', state, action)
switch (action.type) {
case 'TEST':
return {
name: 'tom'
}
case 'CHANGE_NAME':
return {
...state,
name: action.name
}
default:
break;
}
return state
}
//next是dispatch 三层函数 redux源码applyMiddleware(createStore)()
//applyMiddleware 是在dispatch执行前执行middleware钩子 用户dispatch的是前置dispatch 真正的dispatch会作为next函数传到第二层柯里化函数
//next 和 dispatch 不是一个函数 next是最终执行reducer(同步)
// redux-thunk
let thunk = ({dispatch, getState}) => next => action => {
if( typeof action === 'function' ){
// 如果action不是对象是函数, 就将next(真正的dispatch)交给用户写的action函数
//传dispatch是防止多异步
return action(dispatch, getState)
}
//对象执行同步操作
return next(action)
}
const store = createStore(listProcessor, applyMiddleware(thunk))
store.dispatch({
type: 'TEST'
})
console.log(store.getState())
// index.js
import {Provider} from 'react-redux'
import App from './App'
import store from './store/js'
ReactDOM.render(
<Provider store={store}>
<App />
</ Provider>
)
//Tab.jsx
class Tab extends React.Component {
// connect函数会将需要的state注入到props.state中,同时dispatch也会被注入 props.dispatch, state更新会自动更新引用state的组件render
}
export default connect( state => {
// 过滤state
return {}
}, dispatch => {
// 预写dispatch函数
return {
getData(){
dispatch({
type: 'XXX'
})
}
}
}, combin => {
// 合并state
return {
}
})(Tab)
this.state = {
status: 'loadMore
}
render() {
const mapStatus = {
error: <Error />,
loading: <Loading />,
loadMore: <LoadMore />
}
const Component = mapStatus[this.state.status]
return <Component>
}
react-redux源码demo
/**
* @file custom react-redux
* @author karry
*/
import React from 'react'
const ReduxContext = React.createContext(null)
//provider
export class Provider extends React.Component {
render() {
const {store} = this.props
const {children} = this.props
// 穿件组件根节点provider 将store挂载到context
return <ReduxContext.Provider value={store}>
{children}
</ ReduxContext.Provider>
}
}
//connect连接函数
export function connect(mapStateToProps, mapDispatchToProps) {
return function (ConnectComponent) {
return class extends React.Component {
constructor() {
super()
this.state = {
mergedProps: null
}
}
static contextType = ReduxContext
componentDidMount() {
let store = this.context
//监听dispatch, 计算出心得state 刷新视图
store.subscribe( () => {
console.log('state change', store.getState())
let mergedProps = this.computeProps(store)
if(mergedProps !== this.state.mergedProps) {
this.setState({
mergedProps
})
}
})
}
//计算state返回新的state
computeProps(store) {
let stateStore = mapStateToProps(store.getState())
let eventProps = mapDispatchToProps(store.dispatch)
return {
...stateStore,
...eventProps,
dispatch: store.dispatch
}
}
render() {
console.log('context', this.context)
let mergedProps = this.state.mergedProps || this.computeProps(this.context)
return <ConnectComponent {...mergedProps} />
}
}
}
}
相同路由让组件刷新connect外套witchRouter
import {BrowserRouter, Route, Switch} from 'react-router-dom'
const AppContainer = () => {
return <BrowserRouter>
<Switch>
<Route path="/" exact component={App}></Route>
<Route path="/detail" render={ props => {
return (
<div>
<React.Suspense fallback={<div>loading...</div>}>
<Detail />
</React.Suspense>
</div>
)
}}></Route>
</Switch>
</BrowserRouter>
}
ReactDOM.render(
<Provider store={store}>
<AppContainer />
</Provider>,
document.getElementById('root')
);
/**
* @file react-router-dom demo
* @author karry
*/
import React, {Component} from 'react'
/**
* @param {string} path location url
* @param {object} state 透传的state对象
*/
//解析url
function replaceLocation(path, state) {
let pathInfo = /^([^\?]*?)(\?[^#]*?)?(\#.*?)?$/.exec(path)
return {
pathname: pathInfo[1],
search: pathInfo[2],
hash: pathInfo[3],
state
}
}
function createContext() {
return React.createContext(null)
}
// 事件队列
let eventEmitter = {
listener: [],
notify(...args) {
this.listener.forEach( func => {
func(...args)
})
},
listen( func) {
this.listener.push(func)
}
}
//监听路由变化 路由跳转
function createBrowserHistory() {
function listen(func) {
eventEmitter.listen(func)
}
const domListen = func => {
window.addEventListener('popstate', func)
}
domListen( (event) => {
let action = 'POP'
let location = getDomLocation(event.state)
setState({
location,
action
})
})
//路由跳转
function push(path, state) {
let location = replaceLocation(path, state)
let action = 'POP'
window.history.pushState({
state
}, null, path)
setState({
location,
action
})
}
//获取最新history 触发事件队列监听
const setState = (nextState) => {
let history = window.history
console.log(history)
Object.assign(history, nextState)
eventEmitter.notify(history)
}
return {
push,
listen
}
}
//获取location.history path search hash
function getDomLocation(state) {
let window$location = window.location
let pathname = window$location.pathname
let search = window$location.search
let path = window$location.hash
// console.log('getDomLocation', createBrowserHistory(`${pathname}${search}${path}` , state))
// return createBrowserHistory(`${pathname}${search}${path}` , state)
return window$location
}
let RouterContext = createContext()
export class Router extends Component {
constructor(props) {
super(props)
this.state = {
action: '',
location: getDomLocation()
}
// 监听路由变化更新组件
props.history.listen( ({action, location}) => {
console.log('Router!!!! 更新', props.history)
this.setState({
action,
location
})
})
}
render() {
let contextValue = {
location: this.state.location,
history: this.props.history
}
// 将根节点路由值注入provider
return <RouterContext.Provider value={contextValue}>
{this.props.children}
</RouterContext.Provider>
}
}
// 初始化路由信息
export class BrowserRouter extends Component{
constructor(props) {
super()
this.history = createBrowserHistory()
}
render() {
console.log("Browser", this.props.children)
return <Router history={this.history}>
{this.props.children}
</Router>
}
}
function matcher(pathname, location) {
return (new RegExp(pathname)).exec(location.pathname)
}
export class Route extends Component {
static contextType = RouterContext
render() {
console.log('Route render!', this.props, this.context)
let match = matcher(this.props.path, this.context.location)
let DynamicComponent = this.props.component
console.log(match)
return <React.Fragment>
{match ? <DynamicComponent {...this.context} /> : null}
</ React.Fragment>
}
}
export class Link extends Component {
static contextType = RouterContext
render() {
return <a onClick={this.skip.bind(this)}>
{this.props.children}
</a>
}
skip() {
this.context.history.push(this.props.to)
}
}
export class Switch extends Component {
}