Oavsett vilket språk du programmerar i eller vilken typ av app du skapar kommer du förr eller senare att märka att vissa mönster i din kod upprepas. Till exempel, varje gång du skapar en ny presentationskomponent eller en verktygsklass, kan du följa en specifik kodstruktur och skriva avsnitt av kod med liten eller ingen förändring. Detta kallas för boilerplate-kod och även om det kan vara nödvändigt i vissa fall kan det vara mycket tröttsamt att skriva om och om igen.
Den här artikeln kommer att visa hur en sådan tråkig process kan automatiseras med en mycket cool JavaScript-verktygslåda som heter Gulp. Även om exemplen kommer att visas i React Native, kan de principer som används här tillämpas på alla programmeringsspråk.
Låt oss först ta en titt på ett React Native-projekt och undersöka dess kod för att ta reda på varför automatisering behövs och är användbart.
Här är en grundläggande katalogstruktur:

Inget överraskande här, detta är en grundläggande React Native + Redux kodstruktur. Låt oss ta en närmare titt inuti stat/ undermappar:

Nu börjar det bli intressant eftersom dessa filer ger riktiga déjà vu-vibbar. Projektet använder Redux för att hantera tillstånd, så ett av de första besluten en utvecklare måste fatta handlar om att strukturera action creators, action types och reducers.
Just detta React Native-projekt använder modulär filstruktur och varje funktion (t.ex. inloggning, profil, sökning etc.) har sin egen mapp som innehåller tre filer:
Det här är naturligtvis inte det enda eller "bästa" sättet att strukturera Redux-projekt, men det är en helt annan diskussion som ligger utanför ramen för den här artikeln.
Vad vi är Vi är dock intresserade av hur man kan automatisera och snabba upp processen med att skapa nya funktioner. För att kunna göra det måste vi söka efter mönster i dessa filer. Låt oss till exempel se hur var och en av dessa filer ser ut inuti anmälan/ mapp.
export const SIGNUP_REQUEST = "signup_request";
export const SIGNUP_SUCCESS = "signup_success";
export const SIGNUP_FAILURE = "signup_failure";import axios from 'axios';
import * as types from './types';
function signupRequest() {
return {
type: types.SIGNUP_REQUEST,
};
}
function signupSuccess(response) {
return {
type: types.SIGNUP_SUCCESS,
payload: response,
};
}
function signupFailure(error) {
return {
type: types.SIGNUP_FAILURE,
payload: error,
};
}
export function signup(email, password, username, firstName, lastName) {
return async (dispatch) => {
dispatch(signupRequest());
const url = 'https://my-react-native-server.com/api/signup';
try {
const response = await axios.post(url, {
email,
password,
username,
firstName,
lastName,
});
dispatch(signupSuccess(response));
} catch (error) {
dispatch(signupFailure(error.message));
}
};
}import * as types from './types';
const INITIAL_STATE = {
loading: false,
finished: false,
};
export default function (state = INITIAL_STATE, action) {
switch (action.type) {
case types.SIGNUP_REQUEST:
return { ...state, loading: true, finished: false };
case types.SIGNUP_FAILURE:
return { ...state, loading: false, finished: false };
case types.SIGNUP_SUCCESS:
return { ...state, loading: false, finished: true };
default:
return state;
}
}Låt oss nu se hur dessa tre filer ser ut inuti inloggning/ mapp.
export const LOGIN_REQUEST = "login_request";
export const LOGIN_SUCCESS = "login_success";
export const LOGIN_FAILURE = "login_failure";import axios from 'axios';
import * as types from './types';
function loginRequest() {
return {
type: types.LOGIN_REQUEST,
};
}
function loginSuccess(response) {
return {
type: types.LOGIN_SUCCESS,
payload: response,
};
}
function loginFailure(error) {
return {
type: types.LOGIN_FAILURE,
payload: error,
};
}
export function login(username, password) {
return async (dispatch) => {
dispatch(loginRequest());
const url = 'https://my-react-native-server.com/api/login';
try {
const response = await axios.post(url, {
username,
password,
});
dispatch(loginSuccess(response));
} catch (error) {
dispatch(loginFailure(error.message));
}
};
}import * as types from './types';
const INITIAL_STATE = {
loading: false,
finished: false,
};
export default function (state = INITIAL_STATE, action) {
switch (action.type) {
case types.LOGIN_REQUEST:
return { ...state, loading: true, finished: false };
case types.LOGIN_FAILURE:
return { ...state, loading: false, finished: false };
case types.LOGIN_SUCCESS:
return { ...state, loading: false, finished: true };
default:
return state;
}
}Oj, det var många upprepningar! Låt oss sammanfatta allt vi har lärt oss hittills genom att undersöka dessa filer:
types.js, actions.js och reducers.js.inloggning/ mappen har typerna LOGIN_REQUEST, LOGIN_SUCCESS och LOGIN_FAILURE, och anmälan/ mappen har typerna SIGNUP_REQUEST, SIGNUP_SUCCESS och SIGNUP_FAILURE.actions.js filen har tre standardfunktioner som också har fått ett passande namn. Enligt detta, login/actions.js filen har privata loginRequest(), loginSuccess() och loginFailure() funktioner, tillsammans med de exponerade Logga in() funktion som använder dem.anmälan/actions.js filen har privata signupRequest(), signupSuccess() och signupFailure() funktioner, och den exponerade Anmälan() funktion som använder dem.inloggning/ och anmälan/ mappar, medan de exponerade funktionerna Logga in() och Anmälan() är ganska lika men inte samma - vilket är förväntat, eftersom inloggnings- och registreringsåtgärder har olika parametrar, liksom olika slutpunkter och anpassad logik.login/reducers.js och signup/reducers.js är också exakt likadana, den enda skillnaden är namnen på åtgärdstyperna.Du kan föreställa dig att det inte är en särskilt spännande uppgift att skriva all denna boilerplate varje gång vi behöver implementera en ny funktion. Så låt oss ta reda på hur vi kan automatisera den här processen.
Helst skulle vi bara vilja ange ett namn på vår nya funktion, och allt annat skulle genereras åt oss: Tillståndsmappen, tillsammans med typerna, åtgärderna och reducerarna. Alla funktioner skulle namnges efter den funktion vi skapar, och all kod som kan upprepas skulle automatiskt genereras åt oss. Är det här ens möjligt?
Tja, med Gulp, det är det! Så låt oss dyka rakt in och förbereda alla verktyg vi behöver för vårt stora automatiseringsäventyr. Vi kan installera allt med ett kommando:
npm install --save-dev yargs gulp gulp-template
Innan vi börjar skriva vår automatiseringskod måste vi definiera mallar som kommer att användas för att generera vår kod. Så, låt oss skapa mallar/ en katalog i vår projektrot och skapar tre filer i den, som heter types.js, actions.js och reducers.js.
De gulp-mall biblioteket komponerar mallar med hjälp av följande syntax:
function <%= functionName %>() {
// function code
}I det här fallet.., funktionsnamn är en parameter som kommer att tillhandahållas av användaren.
Baserat på de kodlikheter och namngivningsregler som vi diskuterade ovan kommer våra mallfiler att ha en enda parameter - namnoch filerna kommer att se ut så här:
export const <%= name.toUpperCase() %>_REQUEST = '<%= name %>_request';
export const <%= name.toUpperCase() %>_SUCCESS = '<%= name %>_success';
export const <%= name.toUpperCase() %>_FAILURE = '<%= name %>_failure';import * as types from './types';
function <%= name %>Request() {
return {
type: types.<%= name.toUpperCase() %>_REQUEST,
};
}
function <%= name %>Success(response) {
return {
type: types.<%= name.toUpperCase() %>_SUCCESS,
payload: response,
};
}
function <%= name %>Failure(error) {
return {
type: types.<%= name.toUpperCase() %>_FAILURE,
payload: error,
};
}
export function <%= name %>() {
return async (dispatch) => {
dispatch(<%= name %>Request());
try {
const response = 'Success';
dispatch(<%= name %>Success(response));
} catch (error) {
dispatch(<%= name %>Failure(error.message));
}
};
}import * as types from './types';
const INITIAL_STATE = {
loading: false,
finished: false,
};
export default function (state = INITIAL_STATE, action) {
switch (action.type) {
case types.<%= name.toUpperCase() %>_REQUEST:
return { ...state, loading: true, finished: false };
case types.<%= name.toUpperCase() %>_FAILURE:
return { ...state, loading: false, finished: false };
case types.<%= name.toUpperCase() %>_SUCCESS:
return { ...state, loading: false, finished: true };
default:
return state;
}Dessa mallfiler kan användas för att generera motsvarande types.js, actions.js och reducers.js filer för alla funktioner baserat på endast dess namn. Naturligtvis kommer dessa filer så småningom förmodligen att behöva finjusteras - särskilt den sista (exporterade) funktionen i actions.js filen (eftersom den funktionen skiljer sig åt för varje funktion), men allt annat kan användas som det är.
För att koppla ihop allt måste vi slutligen skapa en gulpfile.js i vår projektrot, eftersom det är vad vi kommer att använda för våra Gulp-uppgifter. Baserat på vår nuvarande projektstruktur är vår gulpfile.js kommer att se ut så här:
const { argv } = require('yargs');
const gulp = require('gulp');
const template = require('gulp-template');
gulp.task('createFeature', () => {
const { name } = argv;
if (name && name.length > 0) {
const destinationPath = `src/state/${name}`;
gulp.src('templates/actions.js')
.pipe(template({ name }))
.pipe(gulp.dest(destinationPath));
gulp.src('templates/reducers.js')
.pipe(template({ name }))
.pipe(gulp.dest(destinationPath));
gulp.src('templates/types.js')
.pipe(template({ name }))
.pipe(gulp.dest(destinationPath));
return;
}
console.log('******************************************');
console.log('* ERROR: You must provide a feature name *');
console.log('* HINT: gulp createFeature --name login *');
console.log('******************************************');
});Detta skript använder yargs för att tolka det enda konsolargumentet namnoch genererar sedan alla filer med hjälp av gulp-mall biblioteket och de mallfiler som finns i mallar/ mapp. Vi kan enkelt köra det här skriptet i konsolen.
Om vi till exempel vill generera boilerplate-kod för en funktion som heter Sökbehöver vi bara skriva in konsol:
gulp createFeature --name search
Detta kommer att generera en mapp src/state/search/ och följande filer inuti den:
export const SEARCH_REQUEST = "search_request";
export const SEARCH_SUCCESS = "search_success";
export const SEARCH_FAILURE = "search_failure";import * as types from './types';
function searchRequest() {
return {
type: types.SEARCH_REQUEST,
};
}
function searchSuccess(response) {
return {
type: types.SEARCH_SUCCESS,
payload: response,
};
}
function searchFailure(error) {
return {
type: types.SEARCH_FAILURE,
payload: error,
};
}
export function search() {
return async (dispatch) => {
dispatch(searchRequest());
try {
const response = 'Success';
dispatch(searchSuccess(response));
} catch (error) {
dispatch(searchFailure(error.message));
}
};
}import * as types from './types';
const INITIAL_STATE = {
loading: false,
finished: false,
};
export default function (state = INITIAL_STATE, action) {
switch (action.type) {
case types.SEARCH_REQUEST:
return { ...state, loading: true, finished: false };
case types.SEARCH_FAILURE:
return { ...state, loading: false, finished: false };
case types.SEARCH_SUCCESS:
return { ...state, loading: false, finished: true };
default:
return state;
}
}Inte illa, eller hur? Vi skapade en hel del kod med ett enda kommando! Det här är bara en liten bråkdel av vad Gulp kan göra, men det bör ge dig en grundläggande idé om att automatisera processer för att påskynda utvecklingen och koncentrera dig på logiken istället för att slösa tid på att skriva standardkod.
Trevlig kodning! ☺️