<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
jsx表达式
原生语法
function LikeButton() {
return React.createElement(
'button', {
onClick: () => {
// 执行函数
}
},
'按钮名称'
);
}
ReactDOM.render(React.createElement(LikeButton), document.getElementById('root'));
// 👆React.createElement(LikeButton) === LikeButton();
jsx语法
@babel/preset-react
function LikeButton() {
return <button onClick={() => { }}>按钮名称</button>
}
ReactDOM.render(
<LikeButton />,
document.getElementById('root')
);
CSS Module
import React from 'react';
import style from './App.css';
export default () => {
return (
<h1 className={style.title}>
Hello World
</h1>
);
};
// 编译完
<h1 class="_3zyde4l1yATCOkgn-DBWEL">
Hello World
</h1>
VUE scope
Angular encapsulation:ViewEncapsulation.Native
创建组件的两种方式
class组件(有状态组件
有状态组件主要用来定义交互逻辑和业务数据,使用{this.state.xxx}的表达式把业务数据挂载到容器组件的实例上,然后传递props到展示组件,展示组件接收到props,把props塞到模板里面。
class Welcome extends React.Component {
constructor(props) {
super();
this.state = {
counter: 1,
}
}
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
State
React 把组件看成是一个状态机(State Machines)。通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。
React 里,只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。
this.setState({
counter: 12
});
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
实现原理
const queue = [];
function setState( stateChange ) {
enqueueSetState( stateChange, this );
}
function enqueueSetState( stateChange, component ) {
if ( queue.length === 0 ) { // 如果queue的长度是0,也就是在上次flush执行之后第一次往队列里添加
defer( flush );
}
queue.push( {
stateChange,
component
} );
}
// 清除队列函数
function flush() {
let item;
while( item = queue.shift() ) {
const { stateChange, component } = item;
if ( !component.prevState ) { // 如果没有prevState,则将当前的state作为初始的prevState
component.prevState = Object.assign( {}, component.state );
}
if ( typeof stateChange === 'function' ) { // 如果stateChange是一个方法,也就是setState的第二种形式
Object.assign( component.state, stateChange( component.prevState, component.props ) );
} else { // 如果stateChange是一个对象,则直接合并到setState中
Object.assign( component.state, stateChange );
}
component.prevState = component.state;
}
}
function defer( fn ) {
return Promise.resolve().then( fn );
}
生命周期
componentWillMount 在渲染前调用,在客户端也在服务端。
(类比 created() 、 ngOnInit())
componentDidMount 在第一次渲染后调用,只在客户端。之后组件已经生成了对应的DOM结构,可以通过this.getDOMNode()来进行访问。 如果你想和其他JavaScript框架一起使用,可以在这个方法中调用setTimeout, setInterval或者发送AJAX请求等操作(防止异步操作阻塞UI)。
(类比 mounted() 、 ngAfterContentInit())
componentWillReceiveProps 在组件接收到一个新的 prop (更新后)时被调用。这个方法在初始化render时不会被调用。
(类比 updated() 、 ngOnChanges())
shouldComponentUpdate 返回一个布尔值。在组件接收到新的props或者state时被调用。在初始化时或者使用forceUpdate时不被调用。 可以在你确认不需要更新组件时使用。
componentWillUpdate 在组件接收到新的props或者state但还没有render时被调用。在初始化时不会被调用。
componentDidUpdate 在组件完成更新后立即调用。在初始化时不会被调用。
componentWillUnmount 在组件从 DOM 中移除的时候立刻被调用。
(类比 destroyed() 、 ngOnDestroy())
refs获取DOM节点
myInput = React.createRef();
<input ref={this.myInput} />
// this.refs.myInput
<input ref={ref => this.myInput = ref} />
// this.myInput
createRef源码
function createRef() {
var refObject = {
current: null
};
{
Object.seal(refObject);
}
return refObject;
}
当组件挂载时,将 DOM el元素传递给 ref 的回调
当组件卸载时,则会传递 null
函数组件(无状态组件
接收来自父组件props传递过来的数据,使用{props.xxx}的表达式把props塞到模板里面。无状态组件应该保持模板的纯粹性,以便于组件复用。
function Welcome(props) {
// props接受父组件传参
return <h1>Hello, {props.name}</h1>;
}
Hook
Hook 可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
import React, { useState, useEffect, useRef } from 'react';
function Example() {
const [count, setCount] = useState(0);
const pRef = useRef();
// pRef.current
useEffect(() => {
// 更新DOM之后调用 相当于componentDidMount
return () => {
// 销毁时调用 相当于componentWillUnmount
}
},[count]);
// 没传第二个参数 useEffect执行函数被循环执行
// 第二个参数如果只传一个空数组,函数只会在组件挂载时执行一次 ,相当于 componentDidMount
// 第二个参数不为空数组,数组里数值改变触发函数执行(销毁再加载
return (
<React.Fragment>
<p ref={ pRef }>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</React.Fragment>
);
}
export default Example
useState实现原理
let index = 0;
let stateMap = new Map();
function useState(value) {
stateMap.set(index, stateMap.get(index) || value);
let currentIndex = index;
function fun(value) {
stateMap.set(currentIndex, value);
Render();
}
return [stateMap.get(index++), fun];
}
useEffect实现原理
let _deps; // _deps 记录 useEffect 上一次的 依赖
function useEffect(callback, depArray) {
const hasNoDeps = !depArray; // 如果 dependencies 不存在
const hasChangedDeps = _deps
? !depArray.every((el, i) => el === _deps[i]) // 两次的 dependencies 是否完全相等
: true;
if (hasNoDeps || hasChangedDeps) { // 如果 dependencies 不存在,或者 dependencies 有变化
callback();
_deps = depArray;
}
}
React hooks之useState,useReducer,useEffect原理分析
跨渲染周期存储数据
useRef
跟refs原理当差不差
useContext
useContext 接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 的 value prop 决定。
当组件上层最近的 更新时,该 Hook 会触发重渲染,并使用最新传递给 provider 的 context value 值。
条件渲染、列表
render() {
let { dataList, flag } = this.state;
return (<React.Fragment>
{
dataList.map(d => (<li key={d.id}>{d.name}</li>))
}
{
flag ? <span>true</span> : <span>false</span>
}
</React.Fragment>)
}
事件处理
1. 法1
constructor() {
super();
this.handleClick = this.handleClick.bind(this);
}
render(){
return <button onClick={this.handleClick}>按钮</button>
}
2. 法2
handleClick = () => { //执行内容 }
render(){
return <button onClick={this.handleClick}>按钮</button>
}
3. 法3
handleClick(){ //执行内容 }
render(){
return <button onClick={() => this.handleClick()}>按钮</button>
}
错误写法1:
handleClick(){ // 不会被调用 }
render(){
return <button onClick={this.handleClick()}>按钮</button>
}
原因:
JSX解析过程{}中函数代码会被执行,点击不会执行handleClick。
错误写法2:
handleClick(){ // this 丢失 }
render(){
return <button onClick={this.handleClick}>按钮</button>
}
原因:
在JSX中传递的事件不是一个字符串,而是一个函数(如:onClick={this.handleClick}),此时onClick即是中间变量,所以处理函数中的this指向会丢失。解决这个问题就是给调用函数时bind(this),从而使得无论事件处理函数如何传递,this指向都是当前实例化对象。
通信
props
父=>子
// 父组件
class Parent extends React.Component {
constructor() {
super();
}
render() {
return (
<Child msg='msg!!!' />
)
}
}
// 子组件(函数式
function Child(porps) {
let { msg } = porps
return <p>父组件入参:{msg}</p>
}
// 子组件(class组件
import PropTypes from 'prop-types';
class Child extends React.Component {
constructor(props) {
super();
}
render() {
let { msg } = this.props;
return <p>父组件入参:{msg}</p>
}
}
// 限制参数类型、设置参数默认值
Child.propTypes = {
msg: PropTypes.string.isRequired, // isRequired是否必传
}
Child.defaultProps = {
msg: '默认msg'
}
子=>父
// 父组件
class Parent extends React.Component {
constructor() {
super();
this.state = {
childMsg: ''
}
}
getChildMsg = (val) => {
this.setState({
childMsg: val
})
}
render() {
let { childMsg } = this.state;
return (
{ childMsg }
<Child handleClick={ this.getChildMsg } />
)
}
}
// 子组件
function Child(porps) {
let { handleClick } = porps;
return <button onClick={ () => handleClick('childMsg') }>按钮</button>
}
context
Context 通过组件树提供了一个传递数据的方法,从而避免了在每一个层级手动的传递 props 属性。
// 创建一个上下文的容器(组件), defaultValue可以设置共享的默认数据
const ContextDemo = React.createContext(defaultValue);
<ContextDemo.Provider value={/*共享的数据*/}>
/*里面可以渲染对应的内容*/
</ContextDemo.Provider>
createContext实现原理
const emitter = {
listeners: [],
on: fn => {
emitter.listeners.push(fn);
},
off: fn => {
emitter.listeners.splice(emitter.listener.findIndex(fn), 1);
},
emit: value => {
emitter.listeners.forEach(fn => fn(value));
}
};
function createContext(defaultValue) {
class Provider extends React.Component {
componentDidUpdate() {
emitter.emit(this.props.value);
}
componentDidMount() {
emitter.emit(this.props.value);
}
render() {
return this.props.children;
}
}
class Consumer extends React.Component {
constructor(props) {
super(props);
this.state = { value: defaultValue };
emitter.on(value => {
this.setState({ value });
});
}
render() {
return this.props.children(this.state.value);
}
}
return { Provider, Consumer };
}
接收context的2种方式:Consumer 和 contextType
1. 法1
class ComponentA extends React.Component {
render() {
<ContextDemo.Consumer>
{value => /*根据上下文 进行渲染相应内容*/}
</ContextDemo.Consumer>
}
}
// 同一子组件若要接收多个context,可以使用Consumer方式
2. 法2
class ComponentA extends React.Component {
render() {
let value = this.context;
return {value => /*根据上下文 进行渲染相应内容*/}
}
}
ComponentA.contextType = ContextDemo; // ContextDemo => FilberNode
2. 法3
function ComponentA() {
let context = useContext(ContextDemo);
return (
<p>{context.id}</p>
)
}
父子组件、同级组件通信
import React from "react";
import PropTypes from "prop-types";
class Child1 extends React.Component {
constructor(props, context) {
super();
}
render() {
return (
<div>
<p>{this.context.msg}</p>
<button onClick={() => this.context.callback(new Date().getTime())}>按钮</button>
</div>
)
}
}
Child1.contextTypes = { // 子组件需要通过 contextTypes 指定需要访问的元素
msg: PropTypes.string,
callback: PropTypes.func
}
function Child2(props, context) {
return (<div>
<p>{context.msg}</p>
<button onClick={() => context.callback(Math.round(Math.random() * 100))}>按钮</button>
</div>)
}
Child2.contextTypes = {
msg: PropTypes.string,
callback: PropTypes.func
}
export default class ParentComponent extends React.Component {
constructor() {
super();
this.state = {
msg: "msg"
};
}
getChildContext() { // getChildContext 传递给子组件的属性值
return {
msg: this.state.msg,
callback: this.callback
}
}
callback = (val) => {
this.setState({
msg: `msg${val}`
})
}
render() {
return (<div>
<p>msg:{this.state.msg}</p>
<Child1 />
<Child2 />
</div>)
}
}
ParentComponent.childContextTypes = { // 传递给子组件的属性需要先通过 childContextTypes 来指定
msg: PropTypes.string,
callback: PropTypes.func
};
React-Redux
// store.js
import { createStore } from 'redux';
// state
const defaultState = {
str: '这是字符串'
}
// store
const store = createStore( // 接受一个Reducer函数,Reducer函数接受的是state和actions
(state = defaultState, action) => {
switch (action.type) {
case 'change_str':
state.str = '时间戳:' + action.value
break;
default:
break;
}
return { ...state };
}
)
export default store;
// 组件调用
import React from 'react';
import { connect } from 'react-redux';
const mapStateToProps = (state) => {
return {
str: state.str,
}
}
const mapDispatchToProps = (dispatch) => {
return {
resetStr: (data) => dispatch({
type: 'change_str',
value: data
}),
}
}
const Child = function (props) {
return <React.Fragment>
<button onClick={() => props.resetStr(new Date().getTime())}>{props.str}</button>
</React.Fragment>
}
export default connect(
mapStateToProps, // 第一步
mapDispatchToProps // 第二步
)(Child);
// 调用的组件需要被Provider包裹 通常放在App.js
import { Provider } from 'react-redux'
<Provider store={store}>
<Child />
</Provider>
createStore
function createStore(reducer){
let state = null;
const listenerArr = [];
const getState = () => state;
const dispatch = (action) => {
state = reducer(state, action)
listenerArr.forEach(listener => listener())
}
const subscribe = (listener) => listenerArr.push(listener)
// 这里初始化dispatch的原因是在这之前,state是为null的
// 所以我需要传一个不存在的action去reducer里面,拿到最默认的那个defaultState
// 这个defaultState写在reducer的那个文件里面
dispatch({});
return {
dispatch,
subscribe,
getState,
}
}
Provider
import React, {Component} from 'react'
import {PropTypes} from 'prop-types'
export default class Provider extends Component {
getChildContext() {
return {store: this.props.store}
}
constructor() {
super()
this.state = {}
}
render() {
return this.props.children
}
}
Provider.childContextTypes = {
store: PropTypes.object
}
connect
import {Component} from "react";
import React from "react";
import {PropTypes} from 'prop-types'
const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent => {
class Connect extends Component {
constructor() {
super()
this.state = {}
}
componentWillMount() {
this.unSubscribe = this.context.store.subscribe(() => {
this.setState(mapStateToProps(this.context.store.getState()))
})
}
componentWillUnmount() {
this.unSubscribe()
}
render() {
return <WrappedComponent {...this.state}
{...mapDispatchToProps(this.context.store.dispatch)}/>
}
}
Connect.contextTypes = {
store: PropTypes.object
}
return Connect
})
export default connect
redux-thunk
Redux store 仅支持同步数据流。使用 thunk 等中间件可以帮助在 Redux 应用中实现异步性。可以将 thunk 看做 store 的 dispatch() 方法的封装器;我们可以使用 thunk action creator 派遣函数或 Promise,而不是返回 action 对象。
插槽
import React from "react";
class ChildA extends React.Component {
constructor(props) {
super();
}
render() {
let { slotComp } = this.props
return (<React.Fragment>
<div>
{slotComp}
</div>
</React.Fragment>)
}
}
function ChildB(props) {
return props.children
}
export default class Parent extends React.Component {
constructor() {
super();
}
Comp() {
return <span>slot1.0</span>
}
render() {
return (<React.Fragment>
<ChildA slotComp={this.Comp()} />
<ChildB>
<span>slot2.0</span>
</ChildB>
</React.Fragment>)
}
}