一 组件通信
1.1 props
最常见的通信方式,用于在组件之间传递数据,父组件可以通过props将数据传递给自组件;
1.2 实例方法
这是第二父组件向子组件传递数据的方法,非常有用,父组件通过使用ref引用子组件来直接调用子组件实例的方法,demo如下代码所示
export default class Parent extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
var x = this.foo.myFunc();
console.log(x) // hello
}
render() {
return (
<Child ref={foo => { this.foo = foo }} />
)
}
}
class Child extends Component {
constructor(props) {
super(props);
}
myFunc() {
return "hello";
}
render() {
return (
<div>子组件</div>
)
}
}
1.3 回调函数
子组件给父组件传递数据一般通过回调函数的形式,子组件通过调用父组件传回来的回调函数,将数据传给父组件;
const Child = ({ onClick }) => {
<div onClick={() => onClick('stoney')}>Click Me</div>
}
class Parent extends React.Component {
handleClick = (data) => {
console.log("Parent received value from child: " + data)
}
render() {
return (
<Child onClick={this.handleClick} />
)
}
}
1.4 事件冒泡
这个利用浏览器中DOM的事件机制,和回调函数类似,可以把数据从子组件发送到父组件,当你需要在父组件中捕获来自子组件的DOM事件时,可以采用此方法;
class Child extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>子组件</div>
)
}
}
export default class Parent extends React.Component {
constructor (props) {
super(props);
}
handleClick = (evt) => {
console.log(evt.target)
}
render() {
return (
<div onClick={this.handleClick}>
<Child />
</div>
)
}
}
1.5 全局变量
使用全局变量不是最佳方法,但是比较使用,可以节省大量时间,需要谨慎使用。使用window.x明确定义,便于维护管理。在一个组件的生命周期或者事件监听器里设置window.x全局变量,然后在其他组件里就可以用window.x来读取该数据;
1.6 观察者模式
这种方式可以适用于所有组件通信,一般做法是在componentDidMount生命周期里面订阅事件,在componentWillUnmount里面移除事件,然后在接收事件时调用setState()方法;以下实例通过nodeJS 的event模块实现发布订阅模式;
// App.js
import React, { Component } from 'react';
import List1 from './list1.js';
import List2 from './list2.js';
export default class App extends Component {
render() {
return (
<div>
<List1 />
<List2 />
</div>
);
}
}
// list1.js
import React, { Component } from 'react';
import emitter from './events.js';
class List extends Component {
constructor(props) {
super(props);
this.state = {
message: 'List1',
};
}
componentDidMount() {
// 组件装载完成以后声明一个自定义事件
this.eventEmitter = emitter.addListener('changeMessage', (message) => {
this.setState({
message,
});
});
}
componentWillUnmount() {
emitter.removeListener(this.eventEmitter);
}
render() {
return (
<div>
{this.state.message}
</div>
);
}
}
export default List;
// list2.js
import React, { Component } from 'react';
import emitter from './events.js';
class List2 extends Component {
handleClick = (message) => {
emitter.emit('changeMessage', message);
};
render() {
return (
<div>
<button onClick={this.handleClick.bind(this, 'List2')}>点击我改变List1组件中显示信息</button>
</div>
);
}
}
export default List2;
1.7 Provider,Consumer,Context
react使用Context实现祖代组件向后代组件跨层级传值。
Context API
React.createContext
创建一个Context对象,当React渲染一个订阅了这个Context对象的组件,这个组件会从组件树中离自身最近的那个匹配的Provider中读取到当前的context值;
Context.Provider
Provider接收一个value属性,传递给消费组件,允许消费组件订阅context的变化,一个Provider可以和多个消费组件有对应关系。多个Provider也可以嵌套使用,里层的会覆盖外层的数据。当Provider的value值发生变化时,内部所有的消费组件都会重新渲染,Provider及其内部consumer组件都不受制于shouldComponentUpdate函数,因此当consumer组件在其祖先组件退出更新的情况下也能更新;
Class.contextType
挂载在class上的contextType属性会被重新赋值为一个由React.createContext()创建的Context对象。这能让你使用this.context来消费最近Context上的那个值。你可以在任何生命周期中访问到它,包括render函数。(你只能通过该API订阅单一的context)
Context.Consumer
这里,React组件也可以订阅到context变更,这能让你在函数式组件中完成订阅context。这个函数接收当前的context值,返回一个React节点。传递给函数的value值等同于组件树离这个context最近的Provider提供的value值。如果没有对应的Provider,value参数等同于传递给createContext()的defaultValue。
代码实例
import React, { Component, useContext } from 'react';
const MyContext = React.createContext({});
const MyProvider = MyContext.Provider;
const MyConsumer = MyContext.Consumer;
const MyBGContext = React.createContext({});
const MyBGProvider = MyBGContext.Provider;
const MyBGConsumer = MyBGContext.Consumer;
export default class ContextPage extends Component {
constructor(props) {
super(props);
this.state = {
theme: {color: 'red'},
bgTheme: {background: 'red'},
count: 0
};
}
getRandomColor = () => {
return "#"+("00000"+((Math.random()*16777215+0.5)>>0).toString(16)).slice(-6);
};
changeTheme = () => {
// const {color} = this.state.theme;
this.setState({
theme: {
color: this.getRandomColor()
},
bgTheme: {background: this.getRandomColor()},
count: 0
});
};
render () {
const { theme, bgTheme, count} = this.state;
return (
<div style={{border: "1px solid #ddd", margin: '50px', padding: '20px' }}>
<h3>ContextPage</h3>
<MyProvider value={theme}>
<MyConsumer>{ctx => <Child {...ctx} />}</MyConsumer>
<ClassChild />
<FunctionChild />
</MyProvider>
<MyBGProvider value={bgTheme}>
<MyBGConsumer>{ctx => <Child {...ctx} />}</MyBGConsumer>
<FunctionChild />
</MyBGProvider>
<button onClick = {() => this.setState({count: count + 1})}>
change count: {count}
</button>
<button onClick = {this.changeTheme}>change theme color</button>
</div>
);
}
}
function Child ({color, background}) {
return (
<div style={{border: "1px solid #ddd", margin: '10px', color: color, background: background}}>
Child-{color || background}
</div>
);
}
class ClassChild extends Component {
static contextType = MyContext;
render() {
const {color} = this.context;
console.log("classChild");
return (
<div style={{margin: '10px' }}>
<button
style={{color: color}}
onClick = {() => console.log("log classchild")}>
ClassChild-{color}
</button>
</div>
);
}
}
function FunctionChild(props) {
const context = useContext(MyContext);
const bgContext = useContext(MyBGContext);
console.log("FunctionChild");
const {color} = context;
const {background} = bgContext;
return (
<div style={{border: "1px solid #ddd", margin: '10px', color: color, background: background}}>
FunctionChild: {color || background}
</div>
);
};
注意事项
因为context会使用参考标识符(reference identify)来决定何时进行渲染,这里可能会有一些陷阱,当provider的父组件进行重新渲染时,可能会在consumer组件中触发意外的渲染。(value属性总是被赋值为新的对象)
class App extends Component {
render() {
return (
<Provider value={{something: 'something'}}>
<Toolbar />
</Provider>
);
}
}
为了防止这种情况,将value状态提升到父节点的state里;
class App extends Component {
constructor(props) {
super(props);
this.state = {
value: {something: 'something'}
};
}
render() {
return (
<Provider value={this.state.value}>
<Toolbar />
</Provider>
);
}
}
不熟练时,最好不要在项目中使用context,context一般给第三方库使用;
1.8 路由传参
params
<Route path='/path/:name' component={Path}/>
<link to="/path/2">xxx</Link>
this.props.history.push({pathname:"/path/" + name});
读取参数用:this.props.match.params.name
state
<Route path='/list ' component={Sort}/>
<Link to={{ pathname : '/list ' , state : { name : 'sunny' }}}>
this.props.history.push({pathname:"/list ",state : { name : 'sunny' }});
读取参数用: this.props.location.query.state
search
<Route path='/web/search ' component={Search}/>
<link to="web/search?id=12121212">xxx</Link>
this.props.history.push({pathname:`/web/search?id ${row.id}`});
读取参数用: this.props.location.search
1.9 状态管理工具
mobx, redux, dva,
二 无状态组件和纯组件
2.1 无状态组件
又叫函数式组件,本质就是一个常规函数,接收一个参数props,并返回一个reactElement;
import React from 'react';
/*
函数组件:本质就是一个函数,接收一个参数,参数
是父级传递过来的props,必须又一个返回值,返回
值就是该组件要输出的内容
*/
function Child(props) {
const { name } = props;
return <h1>Hello {name}</h1>
}
function App() {
return (
<div>
<Child name="stoney" />
</div>
);
}
export default App;
不能使用string ref, 使用方式如新:
import React, { createRef } from 'react';
function Child(props) {
const { name } = props;
let ref = createRef();
return <h1 ref={ref} onClick = {() => {
console.log(ref)
}}>Hello {name}</h1>
}
function App() {
return (
<div>
<Child name="stoney" />
</div>
);
}
export default App;
无状态组件没有状态,方法,生命周期,只负责纯展示;主要通过减少继承Component而来的生命周期函数达到性能优化的效果;因为没有生命周期函数,只要父组件的state更新,都会重新render;有以下特点;
- 纯函数
- 输入props,输出jsx
- 没有this,没有实例,没有生命周期,没有state,不能使用string ref;
- 不能扩展其他方法
- 使用函数式组件时,应该尽量减少在函数中声明子组件,否则组件每次更新时,都会重新创建这个函数;
2.2 纯组件
react中纯组件是通过控制shouldComponentUpdate生命周期函数,减少render次数来减少性能损耗的。该组件在shouldComponentUpdate生命周期中通过浅比较( 只比较props和state的内存地址),如果内存地址相同,返回false;其他和Component完全一致;React.PureComponent作用在类组件中,React.memeo作用在函数组件中;如果组件的props和state相同时render内容就一致的情况下就可以使用纯组件;需要结合不可变值使用;
export default class Test extends Component {
constructor(props) {
super(props);
this.state = {
accountNum: '',
initPassword: '',
userName: '',
};
}
onChangeText1 = (text, label) => {
this.setState({accountNum:text.target.value});
}
onChangeText2 = (text, label) => {
this.setState({initPassword:text.target.value});
}
onChangeText3 = (text, label) => {
this.setState({userName:text.target.value});
}
render() {
return (
<div style={{backgroundColor:"#faf7f7"}}>
<InputItem label={'账号:'} holder={'请输入账号'} itemValue={this.state.accountNum} handleChangeText={this.onChangeText1} />
<InputItem label={'初始密码:'} holder={'请输入初始密码'} itemValue={this.state.initPassword} handleChangeText={this.onChangeText2} />
<InputItem label={'姓名:'} holder={'请输入姓名'} itemValue={this.state.userName} handleChangeText={this.onChangeText3} />
</div>
)
}
}
class InputItem extends React.PureComponent {
render() {
let {label,holder,itemValue,handleChangeText} = this.props;
console.log('renderInputItem',itemValue);
return (
<div>
<div style={{fontSize:14,color:'#000'}}>{label}</div>
<input
placeholder={holder}
value={itemValue}
onChange={(text)=>handleChangeText(text,itemValue)}
/>
</div>
)
}
}
上面demo中,如果InputItem不使用纯组件,那么,你修改其中任何一个输入框的时候,其他输入框都会重新render;采用纯组件就只会重新render操作的input;大部分情况下,都可以使用PureComponent组件替换Component;
如果使用PureComponent,state的中的某个值是引用类型,一定要记得更新时,更新引用地址(返回一个新对象)
三 受控组件和非受控组件
3.1 受控组件
受控组件的状态是通过React的状态值state或者props控制
3.2 非受控组件
组件不受React的状态值控制,而是通过dom的特性或者React的ref来控制;
export default class App extends Component {
constructor (props) {
super(props)
this.state = {
name: '',
flag: true
}
this.nameInputRef = React.createRef() // 创建 ref
}
render () {
return (
<div>
{/* 这里使用 defaultValue 而不是value,使用 ref */}
<input defaultValue={this.state.name} ref={this.nameInputRef} />
<button onClick={this.alertName.bind(this)}>alert value</button>
</div>
)
}
alertName () {
const ele = this.nameInputRef.current // 通过 ref 获取 dom 节点
alert(ele.value)
}
}
四 SetState
4.1 不可变值
// 错误的写法
this.setState({
count: this.state.count + 1
})
// 正确的写法
const count = this.state.count + 1
this.setState({ count })
正确修改数组
// 不能使用 push pop splice 等,这样违反了不可变值,会影响 shouldCompententUpdate(浅比较) 判断
this.setState(() => ({
list1: this.state.list1.concat(100), // 追加
list2: [...this.state.list2, 100], // 追加
list3: this.state.list3.slice(0, 3) // 截取
list4: this.state.list4.filter(item => item > 100) // 筛选
}))
正确修改对象
this.setState(() => ({
obj1: Object.assign({}, this.state.obj1, {a: 100}),
obj2: {...this.state.obj2, a: 100}
}))
4.1 SetState是同步还是异步
通常情况下,setState为异步更新数据,比如合成事件,钩子函数,生命周期中为异步,在其他地方,例如setInterval,setTimeout,promise,原生事件中为同步
const count = this.state.count + 1
this.setState({
count: count
}, () => {
// 这个函数没有默认参数
console.log(this.state.count) // 打印更新后的值
})
console.log(this.state.count) // 打印更新前的值
setTimeout(() => {
const count = this.state.count + 1
this.setState({ count })
console.log(this.state.count) // 打印更新后的值
})
componentDidMount () {
document.body.addEventListener('click', () => {
const count = this.state.count + 1
this.setState({ count })
console.log(this.state.count) // 打印更新后的值
})
}
4.2 setState 参数为对象和函数的区别
参数为对象
会被合并,结果只执行一次,类似于Object.assign()
初始值 this.state.count = 0
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
输出值 this.state.count = 1
参数为函数
不会被合并,函数无法合并
初始值 this.state.count = 0
this.setState((prevState, props) => {
return {
count: prevState.count + 1
}
})
this.setState((prevState, props) => {
return {
count: prevState.count + 1
}
})
this.setState((prevState, props) => {
return {
count: prevState.count + 1
}
})
输出值 this.state.count = 3
通过对比,如果需要使用当前组件的状态或者属性值来计算下一个状态值,可以采用函数作为setState的参数;
五 Redux React-Redux
5.1 redux是什么
redux是一个独立专门用于做状态管理的JS库,提供可预测化的状态管理,可以运行在不同的环境(客户端,服务端,原生应用),可以用在react,angular,vue等项目中,但基本于react配合使用;主要用来管理react应用中多个组件共享的状态;
redux就是一个经典的发布订阅器;
5.2 redux 数据流
5.3 什么情况下需要使用redux
- 总体原则:能不用就不用,如果不用比较吃力才考虑使用
- 某个组件的状态,需要共享
- 某个状态需要在任何地方都可以拿到
- 一个组件需要改变全局状态
- 一个组件需要改变另一个组件的状态
5.4 Redux三大核心
1,单一数据源
整个应用的state统一存储在一个Object tree中,并且这个object tree只存在唯一一个store中;
2,state是只读的
唯一改变state的方法就是触发action, action是一个用于描述已发生事件的普通对象
这样就确保了视图和网络请求都不能直接去修改state,相反,它们只能表达想要修改的意图,因为所有的修改都被集中化处理,并且严格按照一个接一个的顺序执行;
store.dispatch({type: 'COMPLETE_TODO', index: 1})
3,使用纯函数来执行修改
Reducers只是一些纯函数,它只接收先前的state和action,并且返回新的state。可以复用,可以控制顺序,传入附加参数;
5.5 Redux的组成
1,state
state对象:通常我们会把应用中的数据存储到一个对象树(Object tree)中进行统一管理,我们把这个对象树称为state, state是只读的,这里需要注意,为了保证数据状态的可维护和测试,不推荐直接修改state中的原数据; 通过纯函数修改state;
在开发react项目中,大致可以把state分为三类
- DomainDate:可以理解成为服务器端的数据,比如:获取用户的信息,商品的列表等等;
- UI State:决定当前UI决定展示的状态,比如:弹框的显示隐藏,受控组件等等
- App State: App级别的状态,比如:当前是否请求的loading,当前路由信息等可能被多个组件去使用到的状态
什么是纯函数:
1,相同的输入永远返回相同的输出
2,不修改函数的输入值
3,不依赖外部环境状态
4,无任何副作用
使用纯函数的好处:
1,便于测试
2,利于重构
2,Action-事件
我们对state的修改是通过reducer纯函数进行的,同时通过传入action来进行具体的操作;
Action是把数据从应用传到store的载体,它是store数据的唯一来源,一般来说,我们可以通过store.dispatch()将action传递给store;Action的特点:
- Action本质就是一个javascript的普通对象
- Action对象内部必须要有一个type属性来表示要执行的动作
- 多数情况下,这个type会被定义成字符串常量
- 除了type字段之外,action的结构随便定义,项目中主要用action创建函数
- 只是描述了有事情要发生,并没有描述如何去更新state;
有两个属性
type属性:表示要进行的动作类型,增删改查。。。
payload属性:操作state的同时传入的数据;
但是这里需要注意的是,我们不直接去调用reducer函数,而是通过Store对象提供dispatch方法来调用;
3,Reducer
Reducer本质上就是一个函数,用来响应发送过来的actions,经过处理,把state发送给store的;注意:
- 在Reducer函数中,需要return返回值,这样store才能收到数据
- 函数会收到两个参数,第一个参数是初始化的state,第二个参数是action;
4,Store
为了对state,reducer,action进行统一管理和维护,我们需要创建一个store对象;Store就是把action与reducer联系到一起的对象,主要职责:
- 维持应用的state
- 提供getState()方法获取state
- 提供dispatch()方法发送action
- 通过subscribe()来注册监听
- 通过subscribe()返回值来注销监听
redux API:
createStore(reducer, [preloadedState], enhancer);
-reducer(Function):接收两个参数,分别是当前的state树和需要处理的action,返回新的state树 ;
-[preloadedState](any): 初始时的state。在同构应用中,你可以决定是否把服务端传过来的state合并(hydrate)后传给它,或者从之前保存的用户会话中恢复一个传给它。 如果你使用combineReducers创建 - reducer,他必须是一个普通对象,与传入的keys保持同样的结构。否则,你可以自由传入任何reducer可理解的内容。 enhancer(Function): Store enhancer是一个组合store creator的高阶函数,返回一个新的强化过的store creator。这里与middleware相似,它也允许你通过复合函数 改变store接口。
返回值(Store): 保存了应用所有state的对象,改变state的唯一方法是dispatch action,你也可以subscribe监听state的变化,然后更新UI;
reducer -reducer(state, action)
store
-getState()
-dispatch(action)
-subscribe(listener)
-replaceReducer(nextReducer)
combineReducers(reducers)
将reducer函数拆分成多个单独的函数,拆分后的每个函数负责独立的state applyMiddleware(...middlewares)中间件
下面代码简单的介绍了redux的用法:
function reducer(state = {
nub: 1,
name: 'stoney'
}, action) {
switch(action.type) {
case "EDIT":
return {
...state,
nub: state.nub + 1
}
}
return state;
}
let store = createStore(reducer);
store.subscribe(() => {
console.log("发生了修改了", store.getState())
})
setInterval(() => {
store.dispatch({
type: 'EDIT'
});
// 默认情况下 dispatch是一个同步操作
// console.log(store.getState())
}, 3000);
5.6 React-redux
react-redux就是redux官方出的用于配合react的绑定库,能够使react组件从redux store中方便的读取数据,并且向store中分发actions以此来更新数据;
redux核心源码
export default function createStore(reducer, preloadedState, enhancer) {
// 实现第二个形参选填
// 只有当第二个参数传入的是中间件才会执行下面的代码
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState;
preloadedState = undefined;
}
let currentReducer = reducer;
let currentState = preloadedState; // 整个应用所有的state都存储在这个变量里
let currentListeners = []; // 订阅传进来的回调函数 <=> Button.addEventListener('click', () => {...})
// 这是一个很重要的设计
let nextListeners = currentListeners;
function getState() {
return currentState;
}
function subscribe(listener) {
if(nextListeners === currentListeners) {
// 浅复制
// 实际上nextListeners就是currentListeners,避免直接操作currentListeners,因为其他地方会调用到currentListeners,从而造成数据不一致。
nextListeners = [...currentListeners];
}
nextListeners.push(listener);
return function unsubscribe() {
if(nextListeners === currentListeners) {
// 浅复制
nextListeners = [...currentListeners];
}
const index = nextListeners.indexOf(listener);
nextListeners.splice(index, 1);
}
}
// Button.addEventListener('click', () => {...})
// Button.removeEventListener('click', () => {...})
function dispatch(action) {
currentState = currentReducer(currentState, action); // 调用reducer来更新数据
const listeners = (currentListeners = nextListeners); // 保证当前的listeners是最新的
for(let i = 0; i < listeners.length; i++) {
listeners[i](); // 依次执行回调函数
}
return action;
}
// 手动触发一次dispatch初始化
dispatch({ type: 'INIT' });
return {
getState,
dispatch,
subscribe
}
}
Redux就是帮我们用一个变量存储所有的State,并且提供了发布功能来修改数据,以及订阅功能来触发回调(但是回调之后干嘛,自己解决)
1,react-redux中两个重要的成员
2,Provider
provider包裹在跟组件的最外层,使得所有的组件都可以拿到state
provider接收到store作为props,然后通过context往下传递,这样react中任何组件都可以通过context获取到store;
provider核心源码
import React from 'react';
import PropTypes from 'prop-type';
export default class Provider extends React.Component {
// context 往所有子组件,孙子组件里传递数据
// props 父组件往子组件里传递数据
// state 组件自身的数据
// 声明一个context数据
getChildContext() {
return { store: this.store }
}
constructor(props, context) {
super(props, context)
this.store = props.store;
}
render() {
return React.Children.only(this.props.children)
}
}
Provider.childContextTypes = {
store: PropTypes.object
}
Provider就是通过React的Context API把数据往下传;
3,connect
- Provider内部组件如果想要使用到state中的数据,就必须要connect进行一层包裹封装;(connect本质是高阶组建)
- connect就是方便我们组件能够获取到store中的state;
connect核心源码:
import React from 'react';
import PropTypes fro 'prop-types';
// HOC
const connect = (mapStateToProps = state => state, mapDispatchToProps = {}) => (WrapComponent) => {
return class ConnectComponent extends React.Component {
static contextTypes = {
store: PropTypes.object
}
constructor(props, context) {
super(props, context);
this.state = {
props: {} // 声明了一个叫props的state
}
}
componentDidMount() {
const { store } = this.context // 从Context 中拿到store对象
store.subscribe(() => this.update()) // 订阅Redux的数据更新
this.update();
}
// 每次数据有更新的时候,就会调用这个放法
updated() {
const { store } = this.context; // 从Context 中拿到store对象
const stateProps = mapStateToProps(store.getState()) // 把stores中的全部数据传递到组件内部
const dispatchProps = mapDispatchToProps(store.dispatch) // 把store。dispatch传到组件内部
// 调用setState 触发组件更新
// 将最新的state以及dispatch合并到当前组件的props上
this.setState({
props: {
...this.state.props,
...stateProps,
...dispatchProps
}
})
}
render () {
// 传入props
return <WrapComponent {...this.state.props}></WrapComponent>
}
}
}
export default connect;
connect就是一个高阶组件,接收Provider传递过来的store对象,并订阅store中的数据,如果store中的数据发生改变,就调用setState触发组件更新;
4,redux是如何将state注入到react组件上去的
- 与react产生关联的是React-Redux库
- Redux的原理就是一个发布订阅器,帮我们用一个变量存储所有的state,并且提供了发布功能来修改数据,以及订阅功能来触发回调;
- 而React-Redux的作用就是订阅Store里的数据的更新,它包含两个重要元素,Provider和connect方法;
- Provider的作用就是通过Context API把store对象注入到React组件上去
- 而connect方法就是一个高阶组件,在高阶组建里通过订阅store中数据的更新,从而通过调用setState方法来触发组件更新;
下面demo介绍了redux, react-redux的用法,实现了一个加数的功能;
index.js代码:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './redux/App';
import store from "./redux/reducer/index"
import {Provider} from 'react-redux';
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
App.js代码:
import React from 'react';
import Inner from './inner';
function App() {
return (
<div>
<Inner name="stoney" />
</div>
)
}
export default App;
inner.js代码:
import React from 'react';
import {connect} from 'react-redux';
function Inner(props) {
// console.log(props);
let {nub, dispatch} = props;
return (
<div>
<p>nub: <strong>{nub}</strong></p>
<button
onClick={() => {
dispatch({
type: 'ADD'
})
}}
>加1</button>
</div>
);
}
// console.log(connect)
export default connect((state, props) => {
// console.log(state, props);
return {
...props,
nub: state.nub
};
})(Inner);
/**
* connect((store.state, props) => {
* // 允许对props和store进行一个合并处理
* return {
* 传递给组件的数据
* };
* })(INner);
*/
/**
* connect(function(store中的state, 父组件传入的props){
* return 要传递给组件的信息
* })(要包装的组件)
*/
reducer/index.js代码:
import {createStore} from 'redux';
function reducer(state = {
nub: 1,
nub2: 2
}, action) {
switch(action.type) {
case 'ADD':
return {
...state,
nub: state.nub + 1
}
}
return state;
}
const store = createStore(reducer);
export default store;
六 Mobx
6.1 Mobx的核心概念
Mobx背后的哲学是:
任何源自应用状态的东西都应该自动地获得;即当状态改变时,所有应用到的状态都会自动更新;
核心概念
- Observable state(可观察的状态):驱动应用的数据
- computed values:计算值,定义在相关数据发生变化时自动更新的值;如果你想创建一个基于当前状态的值时,可以使用computed;
- Reactions:反应,当状态改变时自动发生,但它不是产生一个新的值,而是会产生一些副作用,比如打印到控制台,网络请求,递增的更新React组件树以修补DOM等;
- AutoRun:Mobx中的数据依赖基于观察者模式;
6.2 Redux和Mobx对比
- Redux的编程范式是函数式编程,Mobx是面向对象编程;redux比较复杂,函数式编程思想相对较难,同时需要借助一系列的中间件来处理异步操作和副作用;
- Redux的理想数据是immutable的,意味着状态是只读的,不能直接去修改它,每次都返回一个新的数据,同时使用纯函数;而Mobx数据都是一份引用;状态可变,因此redux支持数据回溯;
- 使用Mobx组件可以做到精确更新,主要因为Mobx的observable;Redux是用dispatch进行广播,通过provider和connect来对比前后差异更新,有时需要自己些ShouldComponentUpdated函数;
- Redux全局只有一个store,mobx往往是多个store;
- 大型项目中,可以使用redux作为全局项目管理,mobx作为组件局部状态管理器来通信;
七 react生命周期
7.1 生命周期概述
7.1.1 React 16.3之前
挂载阶段
constructor
componentwillMount
render
componentDidMount
更新阶段
父组件更新引起的组件更新
componentWillReceiveProps(nextProps);
shouldComponentUpdate(nextProps, nextState);
componentWillUpdate(nextProps, nextState);
render();
componentDidUpdate(prevProps, prevState);
组件自身更新
shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate
卸载阶段
componentWillUnmount
7.1.2 现在
**挂载阶段
**
constructor
static getDerivedStateFromProps(props, state) -- 注意this指向
render
componentDidMount
代码演示
import React, {
Component
} from "react";
class Child extends Component {
constructor(props) {
super(props);
this.state = {
age: 8
}
console.log(1, "初始化", "挂载阶段");
}
// 利用getDerivedStateFromProps替代了ComponentwillMount和componentWillReceiveProps
// 拿到了props或者props做了更新,在这里如果state和props有关联,可以在这里进行关联
static getDerivedStateFromProps(props, state) {
console.log(2, '组件即将挂载', '挂载阶段或即将更新');
return true;
}
// componentWillMount() {
// console.log(2, "即将挂载", "挂载阶段");
// }
componentDidMount() {
console.log(4, "已经挂载入DOM");
}
render() {
console.log(3, "即将把内容渲染进DOM");
let {name, setName} = this.props;
let {age} = this.state;
return (<div id="wrap">
<p>姓名:<input
type="text"
placeholder="请输入新名字"
value={name}
onChange={(e) => {
setName(e.target.value)
}}
/></p>
<p>{name}</p>
<p>年龄:<input
type="text"
placeholder="请输入年龄"
value={age}
onChange={(e) => {
this.setState({age: e.target.value})
}}
/></p>
</div>)
}
}
class App extends Component {
state = {
name: ''
}
setName = (name) => {
this.setState({
name
});
}
render() {
let { name } = this.state;
return <div>
<Child name={name} setName={this.setName} />
</div>
}
}
export default App;
打印结果
1 "初始化" "挂载阶段"
2 "组件即将挂载" "挂载阶段或即将更新"
3 "即将把内容渲染进DOM"
4 "已经挂载入DOM"
更新阶段
父组件引起的组件更新
static getDerivedStateFromProps(props, state)
shouldComponentUpdate()
//componentWillUpdate();
render()
getSnapshotBeforeUpdate();
componentDidUpdate()
组件自身更新
shouldComponentUpdate()
//componentWillUpdate()
render();
getSnapshotBeforeUpdate();
componentDidUpdate()
import React, {Component} from "react";
class Child extends Component {
state = {
age: 8
}
static getDerivedStateFromProps(props, state) {
console.log(1, '组件即将挂载', '挂载阶段或即将更新');
return true;
}
shouldComponentUpdate() {
console.log(2, "即将挂载", "自身状态发生变化");
return true;
}
componentWillUpdate() {
console.log(3, "即将挂载", "即将更新");
}
//组件更新后,即将重新渲染DOM
getSnapshotBeforeUpdate() {
console.log(5, "组件更新完毕即将重新渲染DOM")
let wrap = document.querySelector("#wrap");
return wrap.innerHTML;
}
componentDidUpdate(prevProps, prevState, prevInfo) {
console.log(6, "组件更新完成");
console.log(prevInfo);
}
render() {
console.log(4, "即将把内容渲染进DOM");
let {name, setName} = this.props;
let {age} = this.state;
return (<div id="wrap">
<p>姓名:<input
type="text"
placeholder="请输入新名字"
value={name}
onChange={(e) => {
setName(e.target.value)
}}
/></p>
<p>{name}</p>
<p>年龄:<input
type="text"
placeholder="请输入年龄"
value={age}
onChange={(e) => {
this.setState({age: e.target.value})
}}
/></p>
</div>)
}
}
class App extends Component {
state = {
name: ''
}
setName = (name) => {
this.setState({
name
});
}
render() {
let { name } = this.state;
return <div>
<Child name={name} setName={this.setName} />
</div>
}
}
export default App;
卸载阶段
componentWillUnmount
错误处理
static getDerivedStateFromError();
该方法用来获取子组件抛出的错误,返回的是一个对象,该对象被存储在state中,在后续的render方法中就可以根据这个对象的值来进行处理,如:显示不同的UI
componentDidCatch(error, info);
只能捕获生命周期里的错误,不能捕获事件中的错误,且捕获不了异步错误;主要是父组件捕获子组件的错误;
代码演示
import React, {Component} from "react";
class Child extends Component {
state = {
age: 8
}
componentDidMount() {
let box = document.querySelector("#box");
console.log(box.innerHTML);
}
render() {
let {name, setName} = this.props;
let {age} = this.state;
return (<div id="wrap">
<p>姓名:<input
type="text"
placeholder="请输入新名字"
value={name}
onChange={(e) => {
setName(e.target.value)
}}
/></p>
<p>{name}</p>
<p>年龄:<input
type="text"
placeholder="请输入年龄"
value={age}
onChange={(e) => {
this.setState({age: e.target.value})
}}
/></p>
</div>)
}
}
class App extends Component {
state = {
name: 'stoney',
err: false
}
static getDerivedStateFromError(err) {
return {
err: true
}
}
componentDidCatch(err) {
console.log(err);
if(err) {
this.setState({err: true})
}
}
setName = (name) => {
this.setState({
name
});
}
render() {
let { name, err } = this.state;
return <div>
{err ? "子组件出错了" : <Child name={name} setName={this.setName} />}
</div>
}
}
export default App;
7.1.3 将来
useEffect(); 副作用(用来代替生命周期)
7.2 挂载卸载过程
1,constructor()
constructor()中完成了React数据的初始化,它接受两个参数:props和context,使用super() 传入这两个参数;(只要使用了constructor(),就必须写super(),否则会导致this指向错误)
2,componentWillMount()
componentWillMount()一般用的比较少,更多的用在服务端渲染时使用,代表组件已经初始化,但是DOM未渲染;
3,render();
根据组件的props和state,return一个React元素,不负责组件实际渲染工作,之后由React自身根据此元素去渲染出页面DOM。
4,componentDidMounnt()
组件第一次渲染完成,dom节点已经生成,可以在这个生命周期里调用ajax请求数据,返回数据setState后,组件会重新渲染;
5,componentWillUnmount()
这个生命周期里面完成组件的卸载和销毁,需要注意以下几点:
- 取消所有的定时器
- 移除所有的事件监听
7.3 更新过程
1,componentWillReceiveProps(nextProps);
在接受父组件改变后的props需要重新渲染组件时用到的比较多;通过对比nextProps和this.props,将nextProps的state为当前组件的state,重新渲染组件;
2,ShouldComponentUpdate(nextProps, nextState);
唯一用于可以控制组件重新渲染的生命周期,主要用于性能优化;在这里通过添加部分比较逻辑,return false可以组织组件更新;因为react父组件重新渲染会导致所有的子组件都会重新渲染,因此可以在需要的子组件的该生命周期中做判断;
3,componentWillUpdate(nextProps, nextState);
组件进入重新渲染的流程,这里可以拿到nextProps和nextState;
4,componentDidUpdate(prevProps, prevState);
组件更新完毕后,react只会在第一次初始化后进入ComponentDidMount,之后每次重新渲染都会进入这个生命周期,在这里可以拿到更新前的props和state;
5,render()
render函数会插入jsx生成的dom结构,react会生成一份虚拟dom树,每次组件更新时,react会根据diff算法比较更新前后的新旧dom树,找到最小的有差异的dom节点,重新渲染;
7.4 新增的生命周期
1,getDerivedStateFromProps(nextProps, prevState);
代替componentWillReceiveProps(),旧版本的componentWillReceiveProps方法判断前后两个props是否相同,如果不同,则将新的props更新到相应的state中。这样会破坏state数据单一数据源,导致组件状态不可预测,也会增加组件的重绘次数;
// before
componentWillReceiveProps(nextProps) {
if (nextProps.isLogin !== this.props.isLogin) {
this.setState({
isLogin: nextProps.isLogin,
});
}
if (nextProps.isLogin) {
this.handleClose();
}
}
// after
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.isLogin !== prevState.isLogin) {
return {
isLogin: nextProps.isLogin,
};
}
return null;
}
componentDidUpdate(prevProps, prevState) {
if (!prevState.isLogin && this.props.isLogin) {
this.handleClose();
}
}
在componentWillReceiveProps中,一般会做两件事情,一是根据props来更新state,二是触发一些回调,如动画或页面跳转等;
在老版本中,这两件事都需要在componentWillReceiveProps中去做;新版本中,官方将更新state与触发回调重新分配到了getDerivedFromProps与componentDidUpdate中,使得组件整体的更新逻辑更为清晰。在getDerivedFromProps中还禁止了组件去访问this.props,强制让用户去比较nextProps与prevState中的值,以确保当前用户用到getDerivedFromProps这个生命周期函数时,就是在根据当前的props来更新组件的state,而不是去做其他一些让组件自身状态变得不可预测的事情;
2,getSnapshotBeforeUpdate(prevProps, prevState);
代替componentWillUpdate。常见的componentWillUpdate主要用在组件更新前,读取某个DOM元素状态,并在componentDidUpdate中进行相应的处理。两者区别在于:
在react开启异步渲染模式后,在render阶段读取到的dom元素状态并不总是和commit阶段相同,这就导致在componentDidUpdate中使用componentWillUpdate中读取到的dom元素状态是不安全的,因为这个时候的值可能已经失效了;
getSnapshotBeforeUpdate会在最终的render之前被调用,也就是说在getSnapshotBeforeUpdate中读取到的dom元素状态是可以保证与componentDidUpdate中一致的。此生命周期返回的任何值都将作为参数传递给componentDidUpdate;
八 react中的事件
8.1 原生事件和react事件的区别
react中的事件是绑定到document上面的,原生事件是绑定到dom上面,因此相对绑定的地方来说,dom上的事件要优先于document上的事件执行
8.2 react总体设计
react在事件处理上具有如下优点:
- 几乎所有的事件都代理(delegate)到document,达到性能优化的目的
- 对于每种类型的事件,统一使用拥分发函数(dispatchEven)分发事件
- 事件对象(event)是合成对象(syntheticEvent),不是原生的
8.3 原生事件和混合事件响应顺序
class Demo extends Domponent {
componentDidMount () {
const parentDom = ReactDom.findDOMNode(this)
const childDom = parentDom.queneSelector('.button');
childDom.addEventListen('click',this.onDomClick, false)
}
onDOMClick = (e) => {
console.log('dom event!')
}
onReactClick = (e) => {
console.log('react event!')
}
render () {
return <div onClick={this.onReactClick}>demo</div>
}
}
// dom event! react event!
如果在onDOMClick上调用了冒泡:
onDOMClick = (e) => {
console.log('dom event!')
e.stopPropagation()
}
由于阻止了冒泡,则合成事件就不会被执行,因此只会输出dom event!;
class Demo extends Domponent {
componentDidMount () {
const parentDom = ReactDom.findDOMNode(this)
const childDom = parentDom.queneSelector('.button');
childDom.addEventListener('click',this.onDomClick, false)
childDom.addEventListener('click', this.onDomClick1, false);
}
onDOMClick = (e) => {
console.log('dom event!')
}
onDomClick1 = (e) => {
consle.log('dom1 event!')
}
onReactClick = (e) => {
console.log('react event!')
}
onChildClick = (e) => {
console.log('react1 event!')
e.stopPropagtion()
}
render () {
return <div onClick={this.onReactClick}>
<div onClick={this.onChildClick}>demo</div>
</div>
}
}
输出结果:
dom1 event! dom event! react1 event!
用原生的解释可以说是调用了阻止冒泡的函数之后,在该函数后面要执行的函数就不会被冒泡到,也就不会执行,但是在react中,是在调用了stopPropagtion之后,就会给自己添加一个标识isPropagtionStopped来确定后面的监听器是否执行
结论:
- 合成事件统一注册在document上面,所以原生事件总是比合成事件先执行
- 阻止原生事件的冒泡后,会阻止合成事件的执行