前言
实现ref
- 前面在做虚拟dom时,里面会单独把ref提取出来,这次就用上了。
- ref时初始化时用createRef造个,vdom上赋上 ,打印时就可以在current里获取。
class Counter extends React.Component{
static defaultProps = {name:'yehuozhili'}
constructor(props){
super(props)
this.state={number:0}
this.wrapper=React.createRef()
}
handleClick=()=>{
console.log(this.wrapper)
this.setState((state)=>({number:state.number+1}))
}
render(){
console.log('render')
return (
<div ref={this.wrapper}>
<p>{this.state.number}</p>
{
this.state.number>3?null:<Child number={this.state.number}></Child>
}
<button onClick={this.handleClick}>+</button>
</div>
)
}
}
- 一共有2个地方可以赋ref,一个是类组件实例,一个是原生dom:
function createNativeDOM(element){
let {type,props,ref} =element
let dom = document.createElement(type)
createNativeDOMChildren(dom,element.props.children)
setProps(dom,props)
if(ref){
ref.current=dom
}
return dom
}
function createClassComponetDOM(element){
let {type,props,ref}=element
let componentInstance =new type(props)
if(ref){
ref.current=componentInstance
}
if(componentInstance.componentWillMount){
componentInstance.componentWillMount()
}
if(type.getDerivedStateFromProps){
let newState =type.getDerivedStateFromProps(props,componentInstance.state)
if(newState){
componentInstance.state={...componentInstance,...newState}
}
}
let renderElement = componentInstance.render()
componentInstance.renderElement=renderElement
element.componentInstance=componentInstance
let newDom =createDOM(renderElement)
if(componentInstance.componentDidMount){
componentInstance.componentDidMount()
}
return newDom
}
- 加个判断就可以了。然后createRef实际就是创建个对象
function createRef(){
return {current :null}
}
实现context
- context应用有以下几步:
- 使用createContext创建context
let ThemeContext = React.createContext(null);
- 使用context.provider包裹渲染组件,并传递value,制作生产者。
class FunctionPage extends Component {
constructor(props) {
super(props);
this.state = { color: 'red' };
}
changeColor = (color) => {
this.setState({ color });
}
render() {
let contextVal = { changeColor: this.changeColor, color: this.state.color };
return (
<ThemeContext.Provider value={contextVal}>
<div style={{ margin: '10px', border: `5px solid ${this.state.color}`, padding: '5px', width: '200px' }}>
page
<FunctionHeader />
<FunctionMain />
</div>
</ThemeContext.Provider>
)
}
}
- 消费者通过context的consumer包裹,即可得到生产者提供参数
class FunctionHeader extends Component {
render() {
return (
<ThemeContext.Consumer>
{
(value) => (
<div style={{ border: `5px solid ${value.color}`, padding: '5px' }}>
header
<FunctionTitle />
</div>
)
}
</ThemeContext.Consumer>
)
}
}
function createContext(defaultValue){
Provider.value=defaultValue
function Provider(props){
Provider.value=props.value
return props.children
}
function Consumer(props){
return props.children(Provider.value)
}
return {Provider,Consumer}
}
- 这样就完成了,实际上,被这个组件包裹的元素,也就是这个组件的props.children原本是要渲染的元素,相当于做了个代理,组件内的中间件。
- 在类组件里,如果挂了静态属性contextType,还可以通过this.context.xxx来取得context的属性:
class Header extends Component {
static contextType = ThemeContext;
render() {
return (
<div style={{ border: `5px solid ${this.context.color}`, padding: '5px' }}>
header
<Title />
</div>
)
}
}
- 就是在创建类虚拟dom里把静态属性里面对象的那个值拿出来,赋给实例的context。
function createClassComponetDOM(element){
let {type,props,ref}=element
let componentInstance =new type(props)
if(type.contextType){
componentInstance.context =type.contextType.Provider.value
}
if(ref){
ref.current=componentInstance
}
if(componentInstance.componentWillMount){
componentInstance.componentWillMount()
}
if(type.getDerivedStateFromProps){
let newState =type.getDerivedStateFromProps(props,componentInstance.state)
if(newState){
componentInstance.state={...componentInstance,...newState}
}
}
let renderElement = componentInstance.render()
componentInstance.renderElement=renderElement
element.componentInstance=componentInstance
let newDom =createDOM(renderElement)
if(componentInstance.componentDidMount){
componentInstance.componentDidMount()
}
return newDom
}
function updateClassComponent(oldelement,newelement){
let componentInstance = oldelement.componentInstance
let updater = componentInstance.updater
let nextProps = newelement.props
if(oldelement.type.contextType){
componentInstance.context=oldelement.type.contextType.Provider.value
}
if(componentInstance.componentWillReceiveProps){
componentInstance.componentWillReceiveProps(nextProps)
}
if(newelement.type.getDerivedStateFromProps){
let newState =newelement.type.getDerivedStateFromProps(nextProps,componentInstance.state)
if(newState){
componentInstance.state={...componentInstance,...newState}
}
}
updater.emitUpdate(nextProps)
}
- 因为实例上的context还是老状态,更新了需要用虚拟dom上的新状态把老状态换掉。
将生命周期改为批处理
- 由于前面写的生命周期并不是批处理更新,所以需要更改下。
- 其中react-dom里能解构一个方法,叫
unstable_batchedUpdates
。这个就相当于批量更新的开关。
- 先实现这个方法,然后把生命周期里的函数传给它就行了:
function unstable_batchedUpdates(fn){
updateQueue.ispending=true
fn()
updateQueue.ispending=false
updateQueue.batchUpdate()
}
if(componentInstance.componentDidMount){
unstable_batchedUpdates(()=>componentInstance.componentDidMount())
}
修复空数组bug
- 前面写的还有2个bug,就是对于字符串和产生空数组会有点问题。需要改这几个bug:
- createElement判断有点问题,另外如果空数组情况,就不传children了
export function createElement(type, config, children) {
let propName;
const props = {};
let key = null;
let ref = null;
if (config != null) {
if (hasValidRef(config)) {
ref = config.ref;
}
if (hasValidKey(config)) {
key = '' + config.key;
}
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}
}
const childrenLength = arguments.length - 2;
if (childrenLength === 1) {
if(typeof children==='string'||typeof children==='number'){
children ={$$typeof:REACT_TEXT_TYPE,key:null,content:children,type:REACT_TEXT_TYPE,ref:null,props:null}
}
if(!(children instanceof Array &&children.length===0)){
props.children = children;
}
} else if (childrenLength > 1) {
const childArray = Array(childrenLength);
for (let i = 0; i < childrenLength; i++) {
if(typeof arguments[i + 2] === 'string')arguments[i + 2]= {$$typeof:REACT_TEXT_TYPE,key:null,content:children,type:REACT_TEXT_TYPE,ref:null,props:null}
childArray[i] = arguments[i + 2];
}
props.children = childArray;
}
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
return ReactElement(
type,
key,
ref,
ReactCurrentOwner.current,
props,
);
}
- 做Map时的判断,如果是undefined直接return个空Map:
function getChildrenElementsMap(oldChildrenElements){
let oldChildrenElementsMap={}
if(!oldChildrenElements){
return{}
}
if(!oldChildrenElements.length){
oldChildrenElements=[oldChildrenElements]
}
for(let i=0 ;i<oldChildrenElements.length;i++){
let oldkey = oldChildrenElements[i]?.key ||i.toString()
oldChildrenElementsMap[oldkey]=oldChildrenElements[i]
}
return oldChildrenElementsMap
}
修复渲染时propsbug
- 在render下如果新属性没有变动,但是组件渲染了,会导致render下新的属性是undefined,所以要加个判断就好了:
function shouldUpdate(componentInstance,nextProps,nextState){
let nextPropsT = nextProps?nextProps:componentInstance.props
let scu=componentInstance.shouldComponentUpdate&&!componentInstance.shouldComponentUpdate(nextPropsT,nextState)
componentInstance.props =nextPropsT
componentInstance.state = nextState
if(scu){
return false
}
componentInstance.forceUpdate()
}
修复更新属性不正确bug
- 由于在diff时,老虚拟dom需要更新属性,再进行复用,如果忘记更新属性,可能导致每次渲染量都在原来基础上增加。
- 修改updateElement函数:
function updateElement(oldelement,newelement ){
let currentDom =newelement.dom =oldelement.dom
if(oldelement.$$typeof===REACT_TEXT_TYPE&&newelement.$$typeof===REACT_TEXT_TYPE){
if(currentDom.textContent!==newelement.content)currentDom.textContent=newelement.content
}else if(oldelement.$$typeof===REACT_ELEMENT_TYPE){
updateDomProperties(currentDom,oldelement.props,newelement.props)
updateChildrenElements(currentDom,oldelement.props.children,newelement.props.children)
oldelement.props=newelement.props
}else if(oldelement.$$typeof===FUNCTION_COMPONENT){
updateFunctionComponent(oldelement,newelement)
}else if(oldelement.$$typeof===CLASS_COMPONENT){
updateClassComponent(oldelement,newelement)
}
}
- 这样就基本没有bug了,可以拿下面案例试一下,能正确添加删除就ok。
class Todos extends React.Component {
constructor(props) {
super(props);
this.state = { list: [], text: '' };
}
add = () => {
console.log(this.state)
if (this.state.text && this.state.text.length > 0) {
console.log({ list: [...this.state.list, this.state.text] })
this.setState({ list: [...this.state.list, this.state.text] });
}
}
onChange = (event) => {
this.setState({ text: event.target.value });
}
onDel = (index) => {
this.state.list.splice(index, 1);
this.setState({ list: this.state.list });
}
render() {
console.log(this.state.list)
return(
<div>
<input onChange={this.onChange} value={this.state.text}></input><button onClick={this.add}>+++</button>
<ul>
{this.state.list.map((item,index)=>{
return (
<li key={index}>{item}<button onClick={this.onDel}>x</button></li>
)
})}
</ul>
</div>
)
}
}
let element = React.createElement(Todos, {});
ReactDOM.render(
element,
document.getElementById('root')
);