使用Rollup、Buble等工具实现棋盘表示的自引用类实例枚举
Yes, you absolutely can create this enum-like
Refs collection with all the features you need! Here's a step-by-step implementation that meets your requirements: encapsulation of Ref instances, type-safe traversal, a working move method, and compatibility with your toolchain (ES2015+Flow, Rollup, Buble, flow-remove-types).
Core Implementation (with Flow Types)
First, we'll create a modular setup where Ref instances are restricted to our predefined Refs collection, ensuring immutability and type safety.
// src/refs.js const CONSTRUCTOR_TOKEN = Symbol('RefConstructorToken'); const coordinateMap: Map<string, Ref> = new Map(); // Define valid movement directions type Direction = 'up' | 'down' | 'left' | 'right'; // Helper type for coordinates type RefCoordinates = {| x: number, y: number |}; class Ref { x: number; y: number; id: string; // Restrict instantiation to our internal factory function constructor(token: symbol, id: string, x: number, y: number) { if (token !== CONSTRUCTOR_TOKEN) { throw new Error('Ref instances can only be created via the Refs collection'); } this.id = id; this.x = x; this.y = y; } // Move logic: returns matching Ref or null if out of bounds move(direction: Direction): ?Ref { const newCoords = this._calculateNewCoords(direction); // Use coordinate map for O(1) lookups return coordinateMap.get(`${newCoords.x},${newCoords.y}`) || null; } // Internal helper to compute new coordinates _calculateNewCoords(direction: Direction): RefCoordinates { switch (direction) { case 'up': return { x: this.x, y: this.y - 1 }; case 'down': return { x: this.x, y: this.y + 1 }; case 'left': return { x: this.x - 1, y: this.y }; case 'right': return { x: this.x + 1, y: this.y }; default: throw new Error(`Invalid direction: ${direction}`); } } } // Create our fixed collection of Ref instances (tic-tac-toe example) const Refs = {}; // Factory function to create and register Refs const createRef = (id: string, x: number, y: number): Ref => { const ref = new Ref(CONSTRUCTOR_TOKEN, id, x, y); Refs[id] = ref; coordinateMap.set(`${x},${y}`, ref); Object.freeze(ref); // Make individual instances immutable return ref; }; // Populate the Refs collection Refs.TOP_LEFT = createRef('TOP_LEFT', 0, 0); Refs.TOP_CENTER = createRef('TOP_CENTER', 1, 0); Refs.TOP_RIGHT = createRef('TOP_RIGHT', 2, 0); Refs.MIDDLE_LEFT = createRef('MIDDLE_LEFT', 0, 1); Refs.CENTER = createRef('CENTER', 1, 1); Refs.MIDDLE_RIGHT = createRef('MIDDLE_RIGHT', 2, 1); Refs.BOTTOM_LEFT = createRef('BOTTOM_LEFT', 0, 2); Refs.BOTTOM_CENTER = createRef('BOTTOM_CENTER', 1, 2); Refs.BOTTOM_RIGHT = createRef('BOTTOM_RIGHT', 2, 2); // Add traversal helpers Refs.all = Object.freeze(Object.values(Refs)); // Immutable array of all Refs // Freeze the entire collection to prevent modifications Object.freeze(Refs); // Export only what's needed externally export type { Ref }; export default Refs;
Key Features Explained
Encapsulation & Immutability:
- The
CONSTRUCTOR_TOKENensures no one can create newRefinstances outside our factory function. - All
Refinstances and theRefsobject are frozen to prevent accidental modifications.
- The
Working
moveMethod:- Computes new coordinates based on direction.
- Uses a
coordinateMapfor fast lookups of existingRefinstances (O(1) time instead of looping through all instances). - Returns
nullif the new coordinates are outside our predefined board.
Type Safety:
- Flow enforces valid
Directionvalues, ensuring no invalid movement commands. - The
Reftype guarantees variables are only assigned valid instances from theRefscollection.
- Flow enforces valid
Traversal:
Refs.allprovides an immutable array of allRefinstances, making it easy to loop through the entire board:for (const ref of Refs.all) { console.log(`Ref ${ref.id} at (${ref.x}, ${ref.y})`); }
Toolchain Setup (Rollup + Buble + flow-remove-types)
To compile this code into a production-ready bundle:
Step 1: Install Dependencies
npm install rollup rollup-plugin-buble rollup-plugin-flow-remove-types --save-dev
Step 2: Create Rollup Config (rollup.config.js)
import flowRemoveTypes from 'rollup-plugin-flow-remove-types'; import buble from 'rollup-plugin-buble'; export default { input: 'src/refs.js', output: { file: 'dist/refs.js', format: 'esm', // Choose 'cjs' for CommonJS or 'umd' for universal support sourcemap: true, }, plugins: [ // Strip Flow type annotations first flowRemoveTypes(), // Transpile ES2015+ features to compatible code buble({ transforms: { classes: true, objectRestSpread: true, }, targets: { chrome: 60, firefox: 55, safari: 11, edge: 16, }, }), ], };
Step 3: Add Build Scripts to package.json
{ "scripts": { "build": "rollup -c", "watch": "rollup -c -w" } }
Run npm run build to generate your compiled bundle, or npm run watch for development mode.
Usage Example
Import and use the Refs collection in your application:
// src/app.js import Refs from './refs'; import type { Ref } from './refs'; function logMovement(ref: Ref, direction: Direction) { const targetRef = ref.move(direction); if (targetRef) { console.log(`Moved from ${ref.id} to ${targetRef.id}`); } else { console.log(`Cannot move ${direction} from ${ref.id} (out of bounds)`); } } // Test valid movement logMovement(Refs.CENTER, 'right'); // Output: "Moved from CENTER to MIDDLE_RIGHT" // Test out-of-bounds movement logMovement(Refs.TOP_LEFT, 'up'); // Output: "Cannot move up from TOP_LEFT (out of bounds)"
Optional Improvements
- Pre-Cached Adjacents: For performance, precompute adjacent refs during initialization and store them as properties on each
Ref. - Coordinate Lookup: Add a
Refs.getByCoordinates(x, y)method to directly retrieve a ref by its position. - Board Size Flexibility: Parameterize the board creation to support different sizes (e.g., 8x8 chessboard) instead of hardcoding tic-tac-toe.
内容的提问来源于stack exchange,提问作者Hefeust
相关产品推荐
相关产品推荐

