Skip to content

Instantly share code, notes, and snippets.

@ali-master
Created July 15, 2021 18:46
Show Gist options
  • Save ali-master/5702487bfbc7e20da1dc6e0c4fd9beaf to your computer and use it in GitHub Desktop.
Save ali-master/5702487bfbc7e20da1dc6e0c4fd9beaf to your computer and use it in GitHub Desktop.
Redux from scratch without typescript
function createStore(reducer, preloadedState) {
if(typeof reducer !== "function") {
throw new Error("Reducer should be a function.");
}
if(typeof preloadedState === "function") {
throw new Error("PreloadedState can't be a function.");
}
let currentState = preloadedState;
let currentReducer = reducer;
let currentListeners = [];
let nextListeners = currentListeners;
function getState() {
return currentState;
}
function subscribe(listener) {
if(typeof listener !== "function") {
throw new Error("Listener should be a function");
}
nextListeners.push(listener);
return () => {
const listenerIndex = nextListeners.indexOf(listener);
nextListeners.splice(listenerIndex, 1);
currentListeners = [];
}
}
function dispatch(action) {
if(!isPlainObject(action)) {
throw new Error("Actions should be an object");
}
if(!action.hasOwnProperty("type")) {
throw new Error(`Actions may not have an undefined "type" property. You may misspelled an action type string constant`)
}
try {
currentState = currentReducer(currentState, action);
}catch (e) {
console.warn(e);
}
for (let i = 0; i < nextListeners.length; i++) {
const listener = nextListeners[i];
listener();
}
}
dispatch({
type: "@INIT"
})
return {
getState,
subscribe,
dispatch
}
}
function combineReducers(reducers) {
if(!isPlainObject(reducers)) {
throw new Error("[combineReducers] reducers should be an object.");
}
const reducersKeys = Object.keys(reducers);
const finalReducers = {};
for (let i = 0; i < reducersKeys.length; i++) {
const key = reducersKeys[i];
if(typeof reducers[key] !== "function") {
throw new Error(`No reducer provided for key ${key}`);
}
finalReducers[key] = reducers[key];
}
let shapeAssertionError;
try {
assertReducerShape(finalReducers)
}catch (e) {
shapeAssertionError = e;
}
const finalReducersKeys = Object.keys(finalReducers);
return (state = {}, action) => {
if(shapeAssertionError) {
throw new Error(shapeAssertionError);
}
const nextState = {};
for (let i = 0; i < finalReducersKeys.length; i++) {
const key = finalReducersKeys[i];
const reducer = finalReducers[key];
const prevState = state[key];
const nextStateFromKey = reducer(prevState, action);
if(typeof nextStateFromKey === "undefined") {
const actionType = action.type;
throw new Error(`When called an action of type ${actionType ? String(actionType) : "(unknown type)"}, the slice reducer for key ${key} returned undefined. To ignore an action, you must explicitly return the previous state.`)
}
nextState[key] = nextStateFromKey;
}
return nextState;
}
}
function assertReducerShape(reducers) {
for (const [key, reducer] of Object.entries(reducers)) {
const action = {type: "@INIT"};
const reducerResult = reducer(undefined, action);
if(typeof reducerResult === "undefined") {
throw new Error(`The slice reducer for key ${key} returned undefined during initialization.`)
}
const randomActionTypeValue = randomString();
const $randomAction = {type: randomActionTypeValue};
const randomReducerResult = reducer(undefined, $randomAction);
if(typeof randomReducerResult === "undefined") {
throw new Error(`The slice reducer for key ${key} returned undefined probed with a random action type`);
}
}
}
function randomString() {
return Math.random().toString(16).slice(2);
}
function isPlainObject(inp) {
return kindOf(inp) === "object";
}
function kindOf(inp) {
return Object.prototype.toString.call(inp).slice(8, -1).toLowerCase();
}
function counterReducer(state = 1, action) {
if(action.type === "INC") {
return state + state;
}
return state;
}
function personReducer(state = {name: "Ali", family: "Torki"}, action) {
if(action.type === "UPDATE") {
return {
name: action.payload.name,
family: action.payload.family
};
}
return state;
}
const reducers = combineReducers({
counter: counterReducer,
person: personReducer
})
const store = createStore(reducers);
console.log(store.getState())
const unwatch = store.subscribe(function() {
console.log("Store changes", store.getState())
})
store.dispatch({
type: "INC"
})
console.log(store.getState())
store.dispatch({
type: "INC"
})
console.log(store.getState())
unwatch();
store.dispatch({
type: "UPDATE",
payload: {
name: "John",
family: "Doe"
}
})
console.log(store.getState())
unwatch();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment