-
ReactDOM.createPortal(child, container)
:将子节点渲染到存在于父组件以外的 DOM 节点,即将组件插入到任意DOM节点;-
child
:任何可渲染的 React 子元素,如一个元素、字符串、碎片; -
container
:DOM元素; - 通常讲,组件只能装配到最近的父元素上,而对于对话框、提示框等组件,需要跳出其容器;
Portal
可以把组件放置到DOM树的任何地方。
-
-
Context
:一棵组件树的数据共享;- 在父组件中设置数据,其任意层级的子节点都可以共享此数据,从而避免使用
props
层层向下传递数据;
const {Provider, Consumer} = React.createContext(defaultValue);
-
Provider
发布事件,Consumer
订阅事件,defaultValue
设置默认值; -
Consumer
将从最近的上层Provider
读取数据。
- 在父组件中设置数据,其任意层级的子节点都可以共享此数据,从而避免使用
-
错误边界
:React 16引入的,用于捕获其子组件树 JavaScript 异常,记录错误并展示一个回退的UI(错误组件)
,而不是整个组件树的异常;-
componentDidCatch(error, info)
:error
表示被抛出的错误,info
表示一个含有componentStack
属性的对象; -
try-catch
是命令式代码,错误边界
保留了React原生的声明特性。
-
Redux
-
Redux
:一个纯粹的状态管理系统,npm i redux -S- redux是一个独立的专门用于做状态管理的JS库,并不是react的插件库;
- redux可以用在 React、Angular、Vue 等项目中,但通常与 React 配合使用;
- 功能类似于Vue中的 VueX,集中式管理组件之间的状态共享!
-
工作机制
createStore()
:创建一个指定reducer
的store
对象;-
store
:redux
最核心的管理对象;- 内部维护着
store
、reducer
- 核心方法:
getState()
、dispatch(action)
、subscribe(listener)
- 内部维护着
核心概念
-
action
:标识要执行的行为对象,包括两个属性:type、xxx
const action = { type: 'INCREMENT', //标识属性,值为字符串,唯一,必要属性 data: 2 //数据属性,任意名称和类型,可选 } //创建```action```的工厂函数 const increment = (number) => ({type:'INCREMENT', data: number})
-
reducer
:根据老的state
和action
,产生新的state
的纯函数;export default function counter(state=0, action) { switch(action.type) { case 'INCREMENT': return state + action.data case 'DECREMENT': return state - action.data default: return state } } // 注意:函数返回一个新的状态,不要去直接修改原来的状态!
-
store
:将state
、action
与reducer
联系在一起的对象;- 创建
store
对象;
import {createStore} from 'redux' import reducer from './reducers' const store = createStore(reducer);
-
getState()
:获取state
-
dispatch(action)
:分发action
,触发reducer
函数的调用,产生新的state
-
subscribe(listener)
:注册监听,当产生新的state
时,自动调用。
- 创建
基本使用
npm install --save redux
创建目录 src/redux
,用于存放 action-type.js、actions.js、reducers.js、store.js
- 创建
reducer
模块:reducers.js
,包含n
个reducer
函数,作用是根据老的state
和action
,返回一个新的state
export function counter(state=0, action) { //赋予state一个初始值,state是一个数值 switch(action.type) { case 'INCREMENT': return state + action.data //不要直接修改state,而是根据state的状态,返回一个新的state case 'DECREMENT': return state - action.data default: return state //首次初始化调用时,返回的一个初始值 } }
-
action-type.js
:存放常量字段export const INCREMENT = 'INCREMENT' export const DECREMENT = 'DECREMENT'
-
actions.js
:包含所有action creator
import {INCREMENT, DECREMENT} from './action-type' export const increment = (number)=>({type: INCREMENT, data: number}) export const decrement = (number)=>({type: DECREMENT, data: number})
-
store.js
:包含所有生成的store
对象;import { createStore } from 'redux' import { counter } from './reducers' const store = createStore(counter); export default store //如果创建了多个store,则使用 export 导出
-
index.js
中导入store.js
import App from './components/app' import store from './redux/store' function render() { //把 store 传给App组件 ReactDOM.render(
, document.getElementById('root')) } //执行初始化渲染 render(); //监听更新状态的事件,重新渲染 store.subscribe(render); -
App
组件中导入actions.js
// actions.js中使用 export 多次导出的,要获取整个导出的对象,需要使用 * as xxx import * as actions from '../redux/actions' //获取到 store 对象的状态值 const count = this.props.store.getState(); // 0 //更新状态 this.props.store.dispatch(actions.increment(10)); // 0+10 this.props.store.dispatch(actions.decrement(2)); // 10-2
react-redux
react-redux
是 react
的一个插件,降低 redux
与 react
的代码耦合度;
npm i react-redux --save
基本使用
-
index.js
import App from './components/app' import { Provider } from 'react-redux' import store from './redux/store' ReactDOM.render((
-
App
组件import { connect } from 'react-redux' import PropTypes from 'prop-types' import {increment, decrement} from '../redux/actions' class App extends Component { static propTypes = { //声明接收的属性 count: PropTypes.number.isRequired, increment: PropTypes.func.isRequired, decrement: PropTypes.func.isRequired, } //获取状态值 const { count } = this.props; //调用更新状态的方法 this.props.increment(21); // 0+21 } export default connect( state => ({count: state}), //用count接收当前的状态值{count: state} {increment, decrement} //更新状态的方法:{increment: increment, decrement: decrement} )(App)
-
connect()
:连接组件与redux,传递给组件需要的参数; - 参数一回调的当前状态,参数二传递更是状态的方法;
-
{count: state}
和{increment, decrement}
会被解构之后,传递给App组件:{count, increment, decrement}
-
connect()
返回一个函数,该函数的参数为一个组件,它会包装这个组件,并把count、increment、decrement
传递给这个组件;
... -
- 使用高阶组件的装饰器简化
@connect( state => ({count: state}), //用count接收当前的状态值{count: state} {increment, decrement} //更新状态的方法:{increment: increment, decrement: decrement} ) class App extends Component { static propTypes = { //声明接收的属性 count: PropTypes.number.isRequired, increment: PropTypes.func.isRequired, decrement: PropTypes.func.isRequired, } //获取状态值 const { count } = this.props; //调用更新状态的方法 this.props.increment(21); // 0+21 } export default App
组件拆分
-
React-Redux
将所有的组件分为两大类:UI组件、容器组件;- UI组件:只负责UI的呈现,不带有任何业务逻辑;通过
props
接收数据,不使用任何Redux API
,一般保存在components
目录下; - 容器组件:负责管理数据和业务逻辑,不负责UI的呈现,使用
Redux API
,一般保存在containers
目录下;
- UI组件:只负责UI的呈现,不带有任何业务逻辑;通过
- 组件App本身是UI组件,而
connect()
是Redux API
,进行拆分 - 在src目录下创建两个目录:
containers、components
-
components/counter.jsx
import PropTypes from 'prop-types' export default class Counter extends Component { static propTypes = { //声明接收的属性 count: PropTypes.number.isRequired, increment: PropTypes.func.isRequired, decrement: PropTypes.func.isRequired, } //获取状态值 const { count } = this.props; //调用更新状态的方法 this.props.increment(21); }
-
containers/app.jsx
import {connect} from 'react-redux' import {increment, decrement} from '../redux/actions' import Counter from '../components/counter' export default connect( state => ({count: state}), {increment, decrement} )(Counter)
-
index.js
import App from './containers/app' //引入包装后的App组件 import {Provider} from 'react-redux' import store from './redux/store' ReactDOM.render((
异步事件
-
redux
默认不支持异步处理,需要借助redux
插件(异步中间件)npm i redux-thunk -S npm i redux-logger --save //日志中间件
-
store.js
:createStore()
支持第二个参数import {createStore, applyMiddleware} from 'redux' import thunk from 'redux-thunk' import logger from 'redux-logger' import {counter} from './reducers' //applyMiddleware() 应用中间件,thunk, logger 的顺序不能乱,自动打印日志 const store = createStore(counter, applyMiddleware(thunk, logger)); export default store
-
actions.js
:新增一个异步的action
export const increment = (number)=>({type: INCREMENT, data: number}) //异步的action返回一个函数 export const incrementAsync = (number)=>{ return dispatch => { //异步代码 setTimeout(() => { dispatch(increment(number)); }, 1000) //1s之后去分发一个increment action } }
- 容器组件
containers/app.jsx
:加入incrementAsync
import {increment, decrement, incrementAsync} from '../redux/actions' export default connect( state => ({count: state}), {increment, decrement, incrementAsync} )(Counter)
- 在组件
components/counter.jsx
中接收并调用异步action
static propTypes = { //声明接收的属性 count: PropTypes.number.isRequired, increment: PropTypes.func.isRequired, decrement: PropTypes.func.isRequired, incrementAsync: PropTypes.func.isRequired, } //调用异步action this.props.incrementAsync(22);
redux的调试工具
- chrome浏览器:
redux-devtools
- 依赖包:
npm i redux-devtools-extension --save-dev
- 引入插件:
store.js
import {composeWithDevTools} from 'redux-devtools-extension' const store = createStore(counter, composeWithDevTools(applyMiddleware(thunk)));
多个reducer函数
-
reducers.js
:同时暴露多个reducer
函数import { combineReducers } from 'redux' function counter(state=0, action) { //赋予state一个初始值,state是一个数值 switch(action.type) { case 'INCREMENT': return state + action.data case 'DECREMENT': return state - action.data default: return state } } function comments(state=[], action) { //state是一个数组 switch(action.type) { case 'INCREMENT': //新增一个数组元素 return [action.data, ...state] //不要直接修改state,而是根据state的状态,返回一个新的state case 'DECREMENT': //根据索引删除一个元素 return state.filter((item, index) => index!==action.data) //filter() 不会修改原数组state default: return state //首次初始化调用时,返回的一个初始值 } } export default combineReducers({ counter, comments })
-
combineReducers()
让redux向外暴露的state是一个对象结构:{ counter: 0, comments: [] }
-
store.js
import reducers from './reducers' const store = createStore(reducers, applyMiddleware(thunk)); export default store
- 容器组件
app.jsx
:获取state
时,需要指定当前的state
export default connect( state => ({count: state.counter}), {increment, decrement, incrementAsync} )(Counter)