Skip to content

Instantly share code, notes, and snippets.

@hacker0limbo
Last active February 15, 2020 05:09
Show Gist options
  • Save hacker0limbo/b20148a7e59c0a012c6aaf87271b5b93 to your computer and use it in GitHub Desktop.
Save hacker0limbo/b20148a7e59c0a012c6aaf87271b5b93 to your computer and use it in GitHub Desktop.
react redux 模拟实现
export const name = 'react-redux'
const createDOMFromString = (html) => {
const div = document.createElement('div')
div.insertAdjacentHTML('beforeend', html)
return div
}
const mount = (component, root) => {
root.insertAdjacentElement('beforeend', component.renderDOM())
component.onStateChange = (oldEl, newEl) => {
root.insertAdjacentElement('beforeend', newEl)
root.removeChild(oldEl)
}
}
class Component {
constructor(props={}) {
this.props = props
this.el = null
this.state = {}
}
onStateChange(oldEl, newEl) {
console.log('state changed')
}
setState(newState) {
const oldEl = this.el
this.state = newState
this.el = this.renderDOM()
this.onStateChange(oldEl, this.el)
}
handleClick() {
console.log('clicked')
}
render() {
return '<div></div>'
}
renderDOM() {
this.el = createDOMFromString(this.render())
this.el.addEventListener('click', e => this.handleClick(e), false)
return this.el
}
}
class LikeButton extends Component {
constructor(props) {
super(props)
this.state = {
liked: false
}
}
handleClick() {
this.setState({
liked: !this.state.liked
})
}
render() {
return `
<div>
<button style="color: ${this.props.fontColor}">
${this.state.liked ? 'Unlike it' : 'Like it'}
</button>
<span>${this.state.liked ? '👍' : '👎'}</span>
</div>
`
}
}
const root = document.querySelector('#root')
const likeButton = new LikeButton({ fontColor: 'red' })
mount(likeButton, root)
/**
* 1, 一旦状态发生改变,就重新调用 render 方法(在 setState 里面),构建一个新的 DOM 元素
* 2, 每当 setState 中构造完新的 DOM 元素以后,就会通过 onStateChange 告知外部插入新的 DOM 元素,然后删除旧的元素,页面就更新了
* 3, 调用过程:
* 1, 用户触发 handleClick 监听器
* 2, 调用 setState, 重新设置 state, 并触发 renderDom, 根据 render 返回的 html, 重新渲染视图里有关状态的数据, 并重新绑定监听器
* 3, 调用 onStateChange, 根据 render 之后返回的最新元素插入到页面, 这里 onStateChange 可以自定义
*/
const createStore = (reducer, state) => {
const listeners = []
const subscribe = listener => listeners.push(listener)
const getState = () => state
const dispatch = action => {
state = reducer(state, action)
listeners.forEach(listener => listener())
}
// state 可选, 如果不提供(为 undefined), 那么可以在声明 reducer 的时候提供
// 此时 dispatch 一个空的 action, 在 store 里面初始化 state
if (!state) {
dispatch({})
}
return { getState, dispatch, subscribe }
}
const combineReducers = reducers => {
// reducers 是一个对象: { r1, r2 }
// 将所有的 reducers 集合起来, 对各个 reducer 里面的状态进行集合, 整合成一个新的 state
return (state={}, action) => {
return Object.keys(reducers).reduce((nextState, key) => {
nextState[key] = reducers[key](state[key], action)
return nextState
}, {})
}
}
const counter = (state={ count: 0 }, action) => {
switch(action.type) {
case 'INCREMENT':
return {
...state,
count: state.count += action.count
}
case 'DECREMENT':
return {
...state,
count: state.count -= action.count
}
default:
return state
}
}
const book = (state=['React'], action) => {
switch(action.type) {
case 'ADD_BOOK':
return [
...state,
action.newBook
]
case 'UPDATE_BOOK':
return state.map((book, index) => {
if (index !== action.index) {
return book
}
return action.newBook
})
case 'DELETE_BOOK':
return state.filter((book, index) => index !== action.index)
default:
return state
}
}
const rootReducer = (combineReducers({
counter,
book
}))
const store = createStore(rootReducer)
store.subscribe(() => console.log(store.getState()))
store.dispatch({ type: 'INCREMENT', count: 3})
store.dispatch({ type: 'DECREMENT', count: 1})
store.dispatch({ type: 'ADD_BOOK', newBook: 'Angular' })
store.dispatch({ type: 'DELETE_BOOK', index: 1 })
store.dispatch({ type: 'UPDATE_BOOK', newBook: 'Redux', index: 0 })
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment