React.js 中的 、、 等元素的 value 值如果是受到 React.js 的控制,那么就是受控组件。
React.js 将组件渲染,并且构造 DOM 元素然后塞入页面的过程称为组件的挂载
componentWillMount:组件挂载开始之前,也就是在组件调用 render 方法之前调用。一些组件启动的动作,包括像 Ajax 数据的拉取操作、一些定时器的启动等,就可以放在 componentWillMount 里面进行
componentDidMount:组件挂载完成以后,也就是 DOM 元素已经插入页面后调用。(只调用一次哦!)
componentWillUnmount:组件对应的 DOM 元素从页面中删除之前调用。组件销毁前需要一些数据的清理,例如定时器的清理,就会放在 componentWillUnmount 里面去做。
更新阶段的组件生命周期函数:
shouldComponentUpdate(nextProps, nextState):你可以通过这个方法控制组件是否重新渲染。如果返回 false 组件就不会重新渲染。这个生命周期在 React.js 性能优化上非常有用。
componentWillReceiveProps(nextProps):组件从父组件接收到新的 props 之前调用。
componentWillUpdate():组件开始重新渲染之前调用。
componentDidUpdate():组件重新渲染并且把更改变更到真实的 DOM 以后调用。
我们可以给任意代表 HTML 元素标签加上 ref 从而获取到它 DOM 元素然后调用 DOM API。推荐使用将ref属性设置为回调函数而不是ref使用字符串方式:
React之ref详细用法
this.clock=clock}>
//使用时
{this.clock.clientWidth}
也就是说:我们的组件实现像html标签一样的嵌套功能–>Card就是一个容器型组件
class Card
<div className='card-content'>
{this.props.children}//这是个数组,此时长度为2,分别为下面的div和p
div>
class App
<Card>
<div>1div>
<p>2p>
Card>
dangerouslySetHTML 属性传入对象,这个对象的 __html 属性值就相当于元素的 innerHTML,这样我们就可以动态渲染元素的 innerHTML 结构了。
constructor() {
super()
this.state = {
content: '<h1>React.js 小书h1>'
}
}
...
render () {
return (
<div
className='editor-wrapper'
dangerouslySetInnerHTML={{__html: this.state.content}} />
)
}
PropTypes 规定传入参数的类型;
//需要引入
import PropTypes from 'prop-types'
static propTypes = {
comment: PropTypes.object.isRequired
}
//isRequired 关键字来强制组件某个参数必须传入:
//不需要引入什么
static defaultProps = {//没传参数时候的默认参数
comments: []
}
高阶组件的作用其实不言而喻,其实就是为了组件之间的代码复用。组件可能有着某些相同的逻辑,把这些逻辑抽离出来,放到高阶组件中进行复用。高阶组件内部的包装组件和被包装组件之间通过 props 传递数据。
高阶组件就是一个函数,传给它一个组件,它返回一个新的组件。新的组件使用传入的组件作为子组件。
当某个状态被多个组件依赖或者影响的时候,就把该状态提升到这些组件的最近公共父组件中去管理,用 props 传递数据或者函数来管理这种依赖或着影响的行为。
React.js 的 context 其实像就是组件树上某颗子树的全局变量。
React.js 的 context 就是某个组件只要往自己的 context 里面放了某些状态,这个组件之下的所有子组件都直接访问这个状态而不需要通过中间组件的传递。
使用:
如果一个组件设置了 context,那么它的子组件都可以直接访问到里面的内容,它就像这个组件为根的子树的全局变量。任意深度的子组件都可以通过 contextTypes 来声明你想要的 context 里面的哪些状态,然后可以通过 this.context 访问到那些状态。
context 打破了组件和组件之间通过 props 传递数据的规范,极大地增强了组件之间的耦合性。而且,就如全局变量一样,context 里面的数据能被随意接触就能被随意修改,每个组件都能够改 context 里面的内容会导致程序的运行不可预料。
但是这种机制对于前端应用状态管理来说是很有帮助的,因为毕竟很多状态都会在组件之间进行共享,context 会给我们带来很大的方便。一些第三方的前端应用状态管理的库(例如 Redux)就是充分地利用了这种机制给我们提供便利的状态管理服务。但我们一般不需要手动写 context,也不要用它,只需要用好这些第三方的应用状态管理库就行了。
//父组件
static childContextTypes = {
themeColor: PropTypes.string //这是一个大家都用的状态
}
constructor () {
super()
this.state = { themeColor: 'red' }
}
getChildContext () {
return { themeColor: this.state.themeColor }
}
//子组件:
//子组件要获取 context 里面的内容的话,就必须写 contextTypes 来声明和验证你需要获取的状态的类型,它也是必写的,如果你不写就无法获取 context 里面的状态。Title 想获取 themeColor,它是一个字符串,我们就在 contextTypes 里面进行声明。
static contextTypes = {
themeColor: PropTypes.string
}
render () {
return (
<h1 style={{ color: this.context.themeColor }}>this is a test</h1>
)
}
}
不得改写参数
不能调用系统 I/O 的API
不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果
初始化state和返回一个新对象(reducer是一个纯函数,并不会修改state而是利用es6扩展运算符结合覆盖产生一个新对象)
createStore 接受一个叫 reducer 的函数作为参数,这个函数规定是一个纯函数,它接受两个参数,一个是 state,一个是 action,而正是因为它是一个纯函数所以输入同样的state能渲染一样的视图,这就意味着我们不能在reducer中修改state,因此我们只能做复制做替换,通过Object.assign/…/immutable.js的方式
store.dispatch会自动触发reducer执行,要实现这个功能就是必须把reducer传入到createStore中,返回相应的getState,subscribe,dispatch(相应的功能已经在代码注释中阐明)
middleware是一个可插拔的机制,而中间件的执行原理其实就是对dispatch方法的扩展,比如redux-logger就是在dispatch前后获取旧的和新的state。
关于applyMiddleware源码下面会展示:
// 以下代码都是在这边完全手打的,有出错的地方以后我看到会校对,thx
// 首先需要明确middleware,以redux-logger为例,本质就是操作dispatch
// 和源码不完全一致,但思路一样
function reduxLogger(store){
// 为什么不直接用store.dispatch越过这一步呢,因为我们在这个地方需要一个新的dispatch,且看applyMiddlewares
return function (dispatch){
return function(action){
console.info(`before update ${store.getState()}`);
dispatch(action);
console.info(`before update ${store.getState()}`);
}
}
}
// 关于洋葱模型的compose函数
// compose(f1,f2,f3)(args)===>f3(f2(f1(args))),相对源码我稍微做了一点改动,源码是f1(f2(f3(args)))
// https://github.com/reduxjs/redux/blob/v3.7.2/src/compose.js
function compose(...funcs){
if(funcs.length===0){
return args=>args;
}
if(funcs.length===1){
return funcs[0];
}
return funcs.reduce((res,item)=>args=>item(res(args)));
}
//applyMiddlewares,本质还是对dispatch的扩展
function applyMiddlewares(...middlewares){
return (createStore)=>{
return (reducer)=>{
let store=createStore(reducer);
// 一个函数数组,可以调用dispatch返回新的dispatch
let chains=middlewares.map(item=>item(store));
let newDispatch=compose(...chains)(store.dispatch);
return {
...store,
dispatch:newDispatch
}
}
}
}
react中管理数据的流程是单向的,从派发action到发布订阅渲染视图是一条路走到头。
1.reducer:初始化state和返回一个新对象(reducer是一个纯函数,并不会修改state而是利用es6扩展运算符结合覆盖产生一个新对象)
2.createStore(reducer):store.dispatch会自动触发reducer执行,要实现这个功能就是必须把reducer传入到createStore中
3.store放到context里面,让所有子组件都能访问store(结合6)
4.针对大量重复逻辑和对context依赖性过强,使用connect函数返回高阶组件(connect 函数接受一个组件 WrappedComponent 作为参数,把这个组件包含在一个新的组件 Connect 里面,Connect 会去 context 里面取出 store。)
5.此函数的实现使用函数式编程:接收两个参数:mapStateToProps(告诉connect取哪部分数据)mapDispatchToProps(告诉connect如何触发dispatch)
6.抽离出来的provider:提供store
// Provider组件源码
class Provider extends Component {
// 用来设置context对象,返回的就是子树的context
getChildContext() {
return {
store: this.props.store
};
}
render() {
return this.props.children;
}
}
// 用来校验getChildContext返回的context数据类型
Provider.childContextTypes = {
store: React.PropTypes.object
}
// Provider的使用
class xx extends React.Component{
let store=createStore(reducer);
render(){
return{
<Provider store={store}>
<App />
</Provider>
}
}
}
UI组件特点:
只负责 UI 的呈现,不带有任何业务逻辑 没有状态(即不使用this.state这个变量)
所有数据都由参数(this.props)提供
不使用任何 Redux 的 API
容器组件特点:
负责管理数据和业务逻辑,不负责 UI 的呈现
带有内部状态
使用 Redux 的 API
1.定义 action types
编写 reducer
跟这个 reducer 相关的 action creators(action creators 其实就是返回 action 的函数,这样我们 dispatch 的时候只需要传入数据就可以了:)
dispatch({ type: ‘INIT_COMMENTS’, xxx})
dispatch(initComments(xxx))
4.划分好 smart组件和dumb组件
5.在index.js里面引入
import { Provider } from ‘react-redux’
import { createStore } from ‘redux’
const store = createStore(commentsReducer)
6.在需要传参进行一些操作的子组件(一般为修改state,然后dispatch)中引入:
import { connect } from ‘react-redux’
导出的时候是connect返回的带参数的组件(参数和mapDispatchToProps一致和propTypes一致)
ReactDOM.render(
,
document.getElementById(‘root’)
);
下面讲一下connect 源码:
references:
React-Redux 源码分析(三)-- connect
/**
* 1、从context里获取store
* 2、在componentWillMount 里通过mapStateToProps获取stateProp的值
* 3、在componentWillMount 里通过mapDispatchToProps获取dispatchProps的值
* 4、在componentWillMount 里订阅store的变化
* 5、将获得的stateProp,dispatchProps,还有自身的props合成一个props传给下面的组件
* 6,当然其中包含shouldComponentUpdate的性能优化,selectorFactory函数等等
* @param mapStateToProps
* @param mapDispatchToProps
* @returns {{new(): wrapConnectComponent, prototype: wrapConnectComponent}}
* @constructor
*/
const Connect=(mapStateToProps,mapDispatchToProps)=>{
return (wrappedComponent)=>class wrapConnectComponent extends React.Component{
static contextTypes={
store:PropTypes.object,
};
constructor(props){
super(props);
this.state={
allProps:{},
}
};
componentWillMount(){
const {store}=this.context;
this._updateProps();
store.subscribe(()=>this._updateProps());
};
_updateProps(){
// 收集所有的props,setState
const {store}=this.context;
const stateProps=mapStateToProps?mapStateToProps(store.getState,this.props):{};
const dispatchProps=mapDispatchToProps?mapDispatchToProps(store.getState,this.props):{};
this.setState({
allProps:{
...this.props,
...stateProps,
...dispatchProps,
}
})
};
render(){
const {allProps}=this.state;
return(<wrappedComponent {...allProps} />)
}
}
};
References:
react小书
+new Date()是什么意思?:返回从1970年1月1日0时0分0秒,到该日期的毫秒数(时间戳)
而继承 PureComponent 的原理也很简单,它只不过代替你实现了 shouldComponentUpdate 函数:在函数内对现在和过去的 props / state 进行“浅对比”(shallow comparision,具体内容请移步我的:react学习笔记系列(一)即仅仅是比较对象的引用而不是比较对象每个属性的值),如果发现对象前后没有改变则不执行 render 函数对组件进行重新渲染
其实这样一套相似逻辑在 Redux 中也多次存在,在 redux 中也会对数据进行“浅对比”
import {conenct} from 'react-redux'
function mapStateToProps(state) {
return {
todos: state.todos,
visibleTodos: getVisibleTodos(state),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
//--------------------------------------
function mapStateToProps(state) {
return {
data: {
todos: state.todos,
visibleTodos: getVisibleTodos(state),
}
}
}
注意上面两片代码的区别:
代码片1中:
代码中组件 App 是被 react-redux 封装的组件, react-redux 会假设 App 是一个 Pure Component ,即对于唯一的 props 和 state 有唯一的渲染结果。 所以 react-redux 首先会对根状态(即上述代码中 mapStateToProps的第一个形参 state )创建索引,进行浅对比,如果对比结果一致则不对组件进行重新渲染,否则继续调用mapStateToProps 函数;同时继续对 mapStateToProps 返回的 props 对象里的每一个属性的值(即上述代码中的 state.todos 值和 getVisibleTodos(state) 值,而不是返回的 props 整个对象)创建索引。和shouldComponentUpdate 类似,只有当浅对比失败,即索引发生更改时才会重新对封装的组件进行渲染:这就解决了之前一个问题:store中数据改变,但是比如:使用todos的组件并没有重新渲染:就是state.todos建立的索引没有变,所以要想重新渲染需要:todo:[…state.todos]
代码片2中:
这是一个陷阱,即使 state.todos 和 getVisibleTodos(state) 同样不再发生变化,但是因为每次 mapStateToProps 返回结果{ data: {…} } 中的 data 都创建新的(字面量)对象,导致浅对比总是失败, App 依然会再次渲染
同样,我在学习笔记(3)-react-redux使用问题中也记录过类似的问题。
至此我们知道了:
我们已经知道了 react-redux 中也会对根状态进行浅对比,如果引用发生了改变,才重新渲染组件。 所以当状态需要发生更改时,务必让相应的 reducer 函数始终返回新的对象!修改原有对象的属性值然后返回不会触发组件的重新渲染!(所以使用扩展运算符/object.assign/immutable.js)
因此我们可以总结一下connect的作用
references:
React通过redux-persist持久化数据存储
vuex-persist插件为Vuex持久化存储而生
vue单页面应用刷新网页后vuex的state数据丢失的解决方案
解决vuex在页面刷新后数据被清除的问题
为什么会丢失?
关于redux为什么丢失的问题,我并没有搜到答案,但是vuex刷新数据丢失是因为数据是存在内存里面导致刷新必然出现数据的重置,刷新页面,以前申请的内存被释放,重新加载脚本代码,变量重新赋值。
怎么解决丢失问题?